CommonJS、AMD、CMD、ES6模块

187 阅读4分钟

代码的模块化,一个模块就是一个文件,使用脚本时提倡以module ID替代URL地址。模块不同于传统的脚本文件,它良好地定义了一个作用域来避免全局名称空间污染。它可以显式地列出其依赖关系,并以函数(定义此模块的那个函数)参数的形式将这些依赖进行注入,而无需引用全局变量。

script标签的js下载是异步的,解析是同步的。async主要是指定异步解析,仅能用于不依赖于其他js文件的js文件。

1、CommonJS

CommonJS定义的模块分为:模块引用(require)、 模块定义(exports)、 模块标识(module)。

// a.js
const b = require('./b');

//b.js
module.exports = {
    bb: 'bbb'
}
或
exports.bb = 'bbb';

CommonJS 的一个模块,就是一个脚本文件。require命令第一次加载该脚本,就会执行整个脚本,然后在内存生成一个对象。再次执行require命令,也不会再次执行该模块,而是到缓存之中取值。exports是module的一个属性,如下module的值

Module {
  id: '.',
  exports: {},
  parent: null,
  filename: '/xmpp/module.js',
  loaded: false,
  children:[ Module {
       id: '/xmpp/module2.js',
       exports: [Object],
       parent: [Circular],
       filename: '/xmpp/module2.js',
       loaded: true,
       children: [],
       paths: [Array] } ],
  paths:[ 
    '/xmpp/node_modules',
    '/node_modules',
    '/node_modules' ] 
}

require是同步的,对服务器端不是一个问题,因为所有的模块都存放在本地硬盘,可以同步加载完成,等待时间就是硬盘的读取时间。但是,对于浏览器,这却是一个大问题,因为模块都放在服务器端,等待时间取决于网速的快慢,可能要等很长时间,浏览器处于"假死"状态。CommonJS是主要为了JS在后端的表现制定的,它是不适合前端的。

NodeJS是CommonJS规范的实现,webpack 也是以CommonJS的形式来书写

参考文章,CommonJS

简要理解CommonJS规范

2、AMD 模块规范

AMD(Asynchronous Module Definition)是异步模型规范。浏览器端的模块,不能采用同步加载,只能采用异步加载,这就是AMD规范诞生的背景。所有依赖这个模块的语句,都定义在一个回调函数中,等到加载完成之后,这个回调函数才会运行。

模块引用,代码如下:

require([module], callback);
require(['math'], function (math) {
    math.add(2, 3);
});

模块的定义,代码如下:

define(id?, dependencies?, factory);

dependencies表示该模块依赖的其他所有模块标识,模块依赖必须在真正执行具体的factory方法前解决,AMD 运行时核心思想是「Early Executing」,也就是提前执行依赖

 define("alpha", ["require", "exports", "beta"], function (require, exports, beta) {
       exports.verb = function() {
           return beta.verb();
           //Or:
           return require("beta").verb();
       }
   });

模块导出,代码如下:

define(function (require, exports, module) {
    var a = require('a'),
        b = require('b');

    exports.action = function () {};
});

参考文章,AMD AMD 的 CommonJS wrapping

3、CMD 模块规范

CMD(Common Module Definition)是公共模块规范。在 CMD 规范中,推崇 as lazy as possible。

模块定义,代码的书写格式如下:

define(factory);
define(function(require, exports, module) {
  // 同步加载模块
  var a = require('./a');
  a.doSomething();
});

另外一种用法

define(id?, deps?, factory)

字符串 id 表示模块标识,数组 deps 是模块依赖,如下:

define('hello', ['jquery'], function(require, exports, module) {
  // 同步加载模块
  var a = require('./a');
  a.doSomething();
  
  // 异步加载多个模块,在加载完成时,执行回调
  require.async(['./c', './d'], function(c, d) {
    c.doSomething();
    d.doSomething();
  });
});

注意:带 id 和 deps 参数的 define 用法不属于 CMD 规范,而属于 Modules/Transport 规范。

模块导出方式

define(function(require, exports, module) {

  //方法一,通过 return 直接提供接口
  return {
    foo: 'bar',
    doSomething: function() {}
  };
  
  //方法二,module.exports
  module.exports = {
    foo: 'bar',
    doSomething: function() {}
  };
  
  //方式三,exports
  exports.foo = 'bar';
  exports.doSomething = function() {};
});

参考文章:CMD 模块定义规范

4、AMD 和CMD的区别

CMD 推崇依赖就近,AMD 推崇依赖前置

// CMD
define(function(require, exports, module) {   
    var a = require('./a')   
    a.doSomething()   
    // 此处略去 100 行  
    var b = require('./b') 
    // 依赖可以就近书写   
    b.doSomething()   
    // ... 
})

// AMD 默认推荐的是
define(['./a', './b'], function(a, b) {  
    // 依赖必须一开始就写好    
    a.doSomething()    
    // 此处略去 100 行    
    b.doSomething()
    //...
})

参考文章,AMD 和 CMD 的区别有哪些?

5、SeaJS 与 RequireJS区别

CMD 是 SeaJS 在推广过程中对模块定义的规范化产出。 AMD 是 RequireJS 在推广过程中对模块定义的规范化产出。

参考文章, RequireJS官方文档

SeaJS 与 RequireJS区别

SeaJS与RequireJS最大的区别

LABjs、RequireJS、SeaJS 哪个最好用?为什么?

js模块化编程之彻底弄懂CommonJS和AMD/CMD!

webpack模块化原理-commonjs

6、ES6模块

ES6模块的设计思想,是尽量的静态化,使得编译时就能确定模块的依赖关系,以及输入和输出的变量。CommonJS和AMD模块,都只能在运行时确定这些东西。

动态加载,只有运行时才能得到这个_fs对象

// CommonJS模块
let { stat, exists, readFile } = require('fs');

// 等同于
let _fs = require('fs');
let stat = _fs.stat;
let exists = _fs.exists;
let readfile = _fs.readfile;

静态加载,ES6 模块不是对象,而是通过export命令显式指定输出的代码,再通过import命令输入。

// ES6模块
import { stat, exists, readFile } from 'fs';

静态加载的好处:

  • 可以进行类型检测
  • 不再需要对象作为命名空间
  • 不再必须做成全局变量