JS 模块定义常见的有三种方式,即 AMD, CMD 和 CommonJS。其实还有一个 UMD,他是 CommonJS 和 AMD 揉和在一起而已。不过这些都 out 了,拥抱 ES6 吧。话虽这么说,你让那些不用 ES6 不用 babel 的怎么活,所以还是要了解下滴。

CommonJS

CommonJS 是服务端即 Node.js 采用的模块化方案,我们应该都很熟悉了。例如:

1
2
const fs = require('fs');
fs.readFileSync();

这个过程是同步的,只有成功加载 fs 后才能执行后面的步骤。但在服务器文件都在本地,所以这个问题不大。但这个在浏览器就不合适了,如果文件加载耗时很长,将导致一直等待。

AMD

AMD 全称 Asynchronous Module Definition,意思就是异步模块定义。
用法如下:

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

math 模块的加载和 math.add() 方法的执行不是同步的,这样浏览器就不会假死。
RequireJs 和 CurlJs 实现了 AMD 规范,将他们嵌入网页,就可以在浏览器端进行模块化编程了。
关于 AMD 的详细模块定义可以参考wiki)。这里给出 Underscore 的 AMD 定义方法:

1
2
3
4
5
if (typeof define == 'function' && define.amd) {
define('underscore', [], function() {
return _;
});
}

CMD

CMD 全称 Common Module Definition,意思就是通用模块定义。。
对于依赖的模块,AMD 是提前执行,而 CMD 是延迟执行。AMD 推崇依赖前置,而 CMD 则推崇依赖就近。例如:

1
2
3
4
define(function(require, exports, module) {
const math = require('./math');
math.add(1, 2);
});

CMD 的主要实现是 SeaJS
AMD 预先加载所有依赖,使用的时候才去执行,速度快,可以并行加载多个模块。但这就需要开发的时候把全部依赖都提前定义,不便于开发和阅读,而且部分依赖(弱依赖)可能只在少数情况下使用。
CMD 只有在真正需要的时候才去加载依赖,使用的时候才去定义执行,但这个加载逻辑偏重,耗性能。

UMD

UMD 全称 Universal Module Definition。
UMD 是 AMD 和 CommonJS 的揉和,他优先使用 CommonJS 的加载方式,其次才使用 AMD 的加载方式。
例如:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
(function (window, factory) {
if (typeof exports === 'object') {
module.exports = factory();
} else if (typeof define === 'function' && define.amd) {
define(factory);
} else {
window.eventUtil = factory();
}
})(this, function () {
//module ...
});

其实就是一个服务端和浏览端通用的模块解决方案。

ES6 Module

ES6 在语言规格的层面上实现了模块功能,并且实现非常简单,完全可以替代现有的模块加载方案,成为浏览器和服务端都通用的模块解决方案。

1
import {stat, exists, readFile} from 'fs';

这种做法将只在 fs 模块加载3个方法,其他方法不会进行加载。ES6 可以在编译时就完成模块加载,效率比 CommonJS 的加载方式高。
在浏览器中使用 ES6 模块的语法:

1
<script type="module" src="foo.js"></script>

目前 Node 默认模块格式是 CommonJS,ES6 的模块方案还不支持,但可以通过 babel 来使用。

现在推荐做法是广泛使用 ES6 甚至 ES7 来书写 JavaScript 以提高开发效率,再使用 babel 转码就好了。所以前后端我们也都可以使用 ES6 的 Module 来进行。其他的模块加载方案应该会渐渐退出历史舞台。


ECMAScript6 入门 Module