-
Notifications
You must be signed in to change notification settings - Fork 0
Description
CommonJS
CommonJS 是服务端使用的 JavaScript 模块化规范,使用 require 导入模块,使用 module.exports 将当前模块导出。
API
CommonJS 有三个 API 定义模块:
- require 导入
- exports 导出
- module 模块本身
实例
// main.js
var foo = require("./foo.js")
foo.sayHi();
// foo.js
var sayHi = function(){
console.log("hello world");
}
exports.sayHi = sayHi;
注意: CommonJS 是同步加载文件,所以比较适合服务端的场景
AMD
全称是 Asynchronous Module Definition,从名字上就可以看出,这种模块是异步的。它更适用于浏览器端的场景,因为服务端加载模块主要是通过磁盘,速度比较快,而浏览器端加载模块主要是通过网络,受限于于带宽,可能比较慢,如果依然是采用同步的方式,那么整个应用就会卡住,因此,异步的模块加载方式更适用于浏览器端。这也是 AMD 诞生的初衷。
API
define(id?,dependencies?,factory);
id - string
dependencies - Array
factory - Function | Object
从 define 的签名可以看出 id 和 dependencies 是可选的。
factory
从 API 定义可以看出模块的 factory 可以是 function 和 object。如果是 function, 模块的 loader 会在适当的时机执行 function。
factory 是 function 主要有以下两点好处:
- 模块可以有一个独立运行的环境,只把部分 API 暴露出来
- 可以更好的控制模块的初始化过程
依赖声明
AMD 的依赖声明主要有两种方式:
- 在 dependencies 声明依赖
- 在 factory 中 require 依赖
1.在 dependencies 声明依赖
在 dependencies 中声明参数,然后可以在 factory 中按顺序引用参数。例如
define(["a","b"], function(a, b){})
需要注意的是 dependencies 中会有三个默认参数,"require","exports","module",如果 dependencies 没有没有指定,factory 会接受这三个参数。
2. 在 factory 中 require 依赖
这种这是在用到依赖的地方再去 require,更符合编程的习惯。而且依赖模块比较多的情况下写起来也相对美观一些。这种情况下 factory 的参数默认为 require, exports, modules 。但是真正用到的只有 require,所以其他的可以不用写啦。
define(function(require){
var a = require("a")
return {
sayHi : a.sayHi
}
})
两种方式的对比
方式2更美观,更符合编程习惯。但是方式二的话需要进行正则分析,比较消耗时间,性能上是问题。折中的做法是用方式二书写,然后打包的时候把它转成方式1
循环引用问题
如果定义了一个模块 a,a 中需要引用模块 b,而 b 中又在引用 a ,就会产生循环引用问题。
// a.js
define(function(require){
var b = require("b")
return {
sayHi: "hi",
doSomething: function(){}
}
})
// b.js
define(function(require){
var a = require("a")
a.sayHi();
})
这个肯定会报错,因为 b 在 a 还没有 load 完成就开始调用 a 的方法了

看了 require 的官方文档,提示当出现循环引用问题,首先应该去思考是不是设计上出了问题。那么,是不是所有循环引用的场景都是不合理的呢,这也未必,本文先不讨论循环引用存在的必要性问题。令人高兴的是,循环引用也是有解。上述的代码只需简单调整下,就能正常运行。
define(function(require){
return {
j: function(){
require("a").sayHi()
}
}
})
这样就不会报错了。想知道原理吗?哈哈,不想知道的话就没必要继续读下去了。
再说明原理的时候,首先要弄清楚两个概念,这是我从一个百度大哥写的文章里看到的,装载时依赖 和 运行时依赖
-
- 装载时依赖- 模块在初始化过程就需要用到的依赖模块,我们认为这种依赖是装载时依赖
-
- 运行时依赖- 模块在初始化过程不需要用到,但是在后续的运行过程中需要用到的依赖模块,我们认为这种依赖是运行时依赖
对于这个环,只要双方都是运行时依赖,这个环就是活的,就能跑下去。如果有一边是装载时依赖,并且在另一个模块还没加载完成就开始调用它的方法,显然是会出错的。
上述例子中的模块 b,最开始 a 相对于 b 来说是装载时依赖,这时 a 还没加载完全,所以报错了。然后把 a 调整为运行时依赖,就可以运行了。
- 运行时依赖- 模块在初始化过程不需要用到,但是在后续的运行过程中需要用到的依赖模块,我们认为这种依赖是运行时依赖
CMD 介绍
CMD 是 Sea.js 在推广的时候产生的规范,是阿里的 玉伯 提出来。
它们的区别如下:
作者:玉伯
链接:https://www.zhihu.com/question/20351507/answer/14859415
来源:知乎
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
- 对于依赖的模块,AMD 是提前执行,CMD 是延迟执行。不过 RequireJS 从 2.0 开始,也改成可以延迟执行(根据写法不同,处理方式不同)。CMD 推崇 as lazy as possible.
- CMD 推崇依赖就近,AMD 推崇依赖前置。
看代码:
// CMDdefine(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 的写法,同时还支持将 require 作为依赖项传递,但 RequireJS 的作者默认是最喜欢上面的写法,也是官方文档里默认的模块定义写法。
3. AMD 的 API 默认是一个当多个用,CMD 的 API 严格区分,推崇职责单一。比如 AMD 里,require 分全局 require 和局部 require,都叫 require。CMD 里,没有全局 require,而是根据模块系统的完备性,提供 seajs.use 来实现模块系统的加载启动。CMD 里,每个 API 都简单纯粹。
4. 还有一些细节差异,具体看这个规范的定义就好,就不多说了。
虽然 AMD 和 CMD 前期有一些差异,但在 AMD 后续的版本中已经逐渐填补了。
ES6 的 export、import
ES 6 import 和 export 是从语言层面对模块进行支持,相对于以上的规范, ES 6 的模块是静态的,使得在编译的时候就可以对模块的依赖关系进行分析。
在 ES6 中一个模块就是一个独立的文件。该文件内部的所有变量,外部无法获取。
export 命令用于规定模块的对外接口。
import 命令用于输入其他模块提供的功能。