美文网首页IT面试笔试&&面试经验
js的模块方案:CommonJS、AMD和CMD

js的模块方案:CommonJS、AMD和CMD

作者: 张柳哥 | 来源:发表于2018-02-24 19:48 被阅读45次

    什么是CommonJS、AMD和CMD

    CommonJS、AMD和CMD都是js的模块加载方案,JS在最初设计的时候,并没有模块这种概念,也没有提供将各模块进行灵活组装的机制,有的同学说,<script>标签不就可以吗?<script>是可以帮我们把代码组装起来,但是功能太薄弱,而且因为是标签,所以只能在浏览器里面用,对于我们的nodejs就无能为力了,所以算不上模块解决方案。

    后来nodejs出现之后,js可以横跨前后两端进行开发,模块成了不得不解决的一个问题,一些技术爱好者组成了技术社区,社区的推动下,制定了CommonJS模块的规范,nodejs依照此规范,实现了自己的模块加载机制。

    CommonJs有啥特点

    在CommonJS中,有一个全局性方法require(),用于加载模块。比如我们想要操作文件,就需要fs模块:

    const fs = require('fs')
    fs.readFile('a.txt', 'utf8', function(err, data) {
      console.log(data)
    })
    

    CommonJS是在运行时加载模块的,因为CommonJS认为模块就是对象:

    // CommonJS模块
    const { stat, exists, readFile } = require('fs');
    
    // 等同于
    const _fs = require('fs');
    const stat = _fs.stat;
    const exists = _fs.exists;
    const readfile = _fs.readfile;
    

    上面代码的实质是整体加载fs模块(即加载fs的所有方法),生成一个对象(_fs),然后再从这个对象上面读取 3 个方法。这种加载称为“运行时加载”,因为只有运行时才能得到这个对象,导致完全没办法在编译时做“静态优化”,比如语法分析和类型检验,这是CommonJS的一大不足。

    CommonJS的另外一个不足是模块的加载都是同步的,也就是说:

    const fs = require('fs')
    
    console.log('hello world')
    

    只有等到fs模块加载完成之后,后面的代码才有机会执行,哪怕后面并没有用到fs模块。

    这在服务器端没什么问题,但是在浏览器上却行不通,因为浏览器加载模块的时候,需要通过网络,如果网络出现异常,模块加载卡住,后面的代码就得不到运行,浏览器也会陷入假死状态,我们希望当模块加载失败的时候,一些跟该模块无关的代码依然可以运行,这就需要一种新的模块加载机制——AMD。

    什么是AMD

    AMD也是搞CommonJs的那帮人搞出来的,据说这帮人搞出来的CommonJs在nodejs上效果显著,于是雄心勃勃的向浏览器挺近,但是在内部遇到了很大的分歧,最后形成了三个流派,其中一个流派就推出了AMD规范,AMD的全称是“Asynchronous Module Definition”,即“异步模块定义”,从名字上可以看出,它加载模块的方式是异步的,AMD也采用require()语句加载模块,但是不同于CommonJS,它要求两个参数:

    require([module], callback);
    

    第一个参数[module],是一个数组,里面的成员就是要加载的模块;第二个参数callback,则是加载成功之后的回调函数。如果将前面的代码改写成AMD形式,就是下面这样:

    require(['fs'], function(fs) {
      // to do something
    })
    
    console.log('hello world')
    

    这样模块fs的加载,就不会影响console.log的执行,程序相对来说更坚挺。

    AMD有个缺陷,来看下面这段代码:

    const env = 'dev'
    require(['fs', 'path'], function(fs, path) {
      if (env === 'dev') {
        fs.unlink('./a.txt', err => console.log('文件删除成功!'))
      }
    
      if (env === 'test') {
        console.log(path.sep)
      }
    })
    
    console.log('hello world')
    

    可以看出,虽然代码加载了fspath两个模块,但是在程序正真执行的过程中,有些模块的加载其实是没有必要的,即使加载了,在之后的回调中也没有被用到,这样在一定程度上造成了浪费。

    如何解决AMD的缺陷

    为了解决这个问题,阿里巴巴的一个叫玉伯的前端开发,提出了CMD规范(Common Module Definition),并根据此规范,写了一个专门的模块加载器——sea.js,sea在英文中是大海的意思,这也表达了作者对这个加载器的态度——海纳百川,有容乃大。

    与sea起名的是AMD下的一个加载器,叫RequireJS

    sea在加载模块时也是异步的,它主要的特点是允许你在使用模块的时候才去加载模块:

    const env = 'dev'
    
    define(function(require, exports, module) {
      if (env === 'dev') {
        const fs = require('fs')
        fs.unlink('./a.txt', err => console.log('文件删除成功!'))
      }
    
      if (env === 'test') {
        const path = require('path')
        console.log(path.sep)
      }
    })
    
    console.log('hello world')
    

    编写模块要注意什么

    我们在设计模块的时候,需要尽可能的做到高内聚,低耦合。

    高内聚实际上就是要求我们在开发的时候,遵守单一职责原则,也就是一个模块,里面的各个功能元素(类或者函数)应该只做好自己的本职工作,千万不要当老好人,什么都想搭把手。一个旅游团,导游负责景点讲解,司机负责开车,游客负责卖萌拍照,每个人各司其职,旅游就能有条不紊的进行下去,但是,如果游客想帮司机开车,司机也想替游客讲两个景点故事,虽然是好心,但是容易办坏事。

    低耦合主要是之模块与模块之间的关系尽可能的简单,如果一个模块需要修改,应该尽可能的不会影响到其他模块,避免发生牵一发而动全身的情况,这样本身也能极大的节约我们的时间。

    模块的价值是什么

    模块最大的价值是规范和封装了代码,提高了代码的复用性,只要遵守模块的规则,写出来的模块就可以被各个系统使用,提高代码的流动性,可以说,模块是代码世界的集装箱,通过将代码封装成模块,可以很方便的运输到各个行业的各个系统中运行,从而极大的放大了代码的价值。

    其中,github模块的仓库,是伟大码农的智慧的结晶,也是世界上(可能)最大的基友交友平台。

    相关文章

      网友评论

        本文标题:js的模块方案:CommonJS、AMD和CMD

        本文链接:https://www.haomeiwen.com/subject/yvmgtftx.html