CommonJS
是nodejs使用的模块化规范,一个单独的文件就是一个模块,加载模块使用require方法同步加载,该方法读取一个文件并执行,最后返回文件内部的exports对象。
//example.js
exports.message = "hi";
exports.say = function (){
console.log("hello");
};
var example = require('./example.js');
example.say();
如果被require函数引入的模块中也包含依赖,那么依次加载这些依赖。
AMD/RequireJS
CommonJS加载模块是同步的,即模块加载完成才能执行后面的操作。这是因为在服务器环境下,文件一般都在本地磁盘,加载起来比较快。
而在浏览器端就最好使用AMD,即异步加载模块,允许指定回调函数.
用全局函数define来定义模块:
define(id?, dependencies?, factory);
- id:模块名,这个参数是可选的。如果没有提供该参数,则模块名默认为脚本文件的名称。
- dependencies:是个定义中模块所依赖模块的数组。
- factory:工厂方法,为模块初始化要执行的函数或对象。
如果为函数,它应该只被执行一次。
如果是对象,此对象应该为模块的输出值。
如果工厂方法返回一个值(对象,函数,或任意强制类型转换为true的值),应该为设置为模块的输出值。
几个例子:
define(["alpha"],function(alpha){
return {
verb:function(){
return alpha.verb()+2;
}
}
})
//一个没有依赖性的模块可以直接定义对象:
define({
add:function(x,y){
return x+y;
}
})
兼容CommonJS的写法:
define(function(require,exports,module){
var a = require('a');
var b = require('b');
exports.action = funciton(){……}
})
AMD可以使用全局变量require来加载模块:
var a = require('a');
require(['a', 'b'], function (a, b) {
//modules a and b are now available for use.
});
AMD运行的核心是 提前执行,也就是提前执行依赖:
define(['a','b'],function(A,B){
……
})
也就是说在factory执行之前,a,b都下载并执行完毕了。
好处是可以尽早发现错误:如果模块中出现异常,那么factory不会执行。
坏处就是容易产生带宽和内存的开销,因为很有可能我们依赖的模块其实并没有被用到,但还是下载和执行完毕了。
但是AMD也提供懒加载的形式:
define(function(require,exports,module){
console.log("main");
require(['a'],function(a){
a.hello();
});
$('#b').click(function(){
require(['b'], function(b){
b.hello();
});
});
})
我们并没有提前声明依赖,那么在此模块执行时就不会预先加载a和b,在require的时候才会去加载。
这种懒加载方式会大大减轻初始化时的损耗,但是弊端就是后续要执行a.hello()和b.hello()时,需要实时下载代码然后执行,这样可能会带来操作的卡顿。
CMD/SeaJS
CMD更贴近 CommonJS规范,一个模块就是一个文件;
它推崇依赖就近,想什么时候 require 就什么时候加载,并且不使用回调函数的形式。
define
和AMD规范类似,都是用define来定义模块,:
define(id?, deps?, factory)
factory 为函数时,表示是模块的构造方法。执行该构造方法,可以得到模块向外提供的接口。
factory 方法在执行时,默认会传入三个参数:require、exports 和 module:
define(function(require,exports,module){
……
})
define('hello', ['jquery'], function(require, exports, module) {
// 模块代码
});
require
require 是一个方法,接受 模块标识 作为唯一参数,用来获取其他模块提供的接口。这里的require是同步的,并没有使用回调函数。
define(function(require,exports){
var a = require('./a');
a.doSomething();
})
SeaJS 会检查你的模块factory,找到所有的 require 语句,从而得到你的模块的所有依赖。 在真正 require 当前模块时,会先去请求这个模块的依赖,加载完毕,再去初始化当前的模块。
require.async 方法用来在模块内部异步加载模块,并在加载完成后执行指定回调:
define(function(require, exports, module) {
// 异步加载一个模块,在加载完成时,执行回调
require.async('./b',function(b){
b.doSomething();
})
// 异步加载多个模块,在加载完成时,执行回调
require.async(['./c', './d'], function(c, d) {
c.doSomething();
d.doSomething();
});
})
注意:require 是同步往下执行,require.async 则是异步回调执行。 require.async 一般用来加载可延迟异步加载的模块。
exports
exports 是一个对象,用来向外提供模块接口。
define(function(require, exports) {
// 对外提供 foo 属性
exports.foo = 'bar';
// 对外提供 doSomething 方法
exports.doSomething = function() {};
});
除了给 exports 对象增加成员,还可以使用 return 直接向外提供接口。
define(function(require) {
// 通过 return 直接提供接口
return {
foo: 'bar',
doSomething: function() {}
};
});
与 RequireJS 的 AMD 规范相比,CMD 与 CommonJS 和 Node.js 的 Modules 规范保持了很大的兼容性。通过 CMD 规范书写的模块,可以很容易在 Node.js 中运行
看出CMD的理念是就近加载。我们可以在需要用到依赖的时候才require,并且不使用回调函数的形式
在导出模块方面,既支持exports导出,也支持直接AMD的直接return导出
ES6的模块
ES6模块与上面的都不同,是静态加载的,也就是在编译阶段进行加载,在编译时就确定模块的依赖关系,以及输入和输出的变量。
而CommonJS、AMD都是在运行时才确定这些关系和输入输出什么的。
- 运行时加载: CommonJS/AMD 模块就是对象;在输入时是先加载整个模块,生成一个对象,然后再从这个对象上面读取方法,这种加载称为“运行时加载”
- 编译时加载:ES6 模块不是对象,而是通过 export 命令显式指定输出的代码,输入时采用静态命令的形式。即在输入时可以指定加载某个输出值,而不是加载整个模块,这种加载称为“编译时加载
ES6模块的运行机制与CommonJS不一样,它遇到模块加载命令import时,不会去执行模块,而是只生成一个动态的只读引用。等到真的需要用到时,再到模块里面去取值,ES6模块是动态引用。
- 在 CommonJS 中,导入(imports)是模块导出值的复制值(同时 require()动作是同步的)。也就是说,在一个模块中一个值和这个值导出被复制后的值不是同一个,两者之间不存在连接
- 在 ES6 中,导入是对导出值的只读。因此,模块中的值和导出后的值是同一个,存在连接,只是在导入的模块中对这个值是只读的。
“只读”说明在导入的模块中不能直接修改被导入的值,如果要修改被导入的值,可以通过调用被导入模块的函数来达到目的。