JavaScript 模块加载的相关规范

CommonJS 规范

定义的模块分为3部分: require(引用), exports(导出), module(本身)。规定一个文件就是一个模块,文件中定义的变量、函数、类,都是私有的,其他文件是不可见。加载模块是同步的,所以只有加载完成才能执行后面的操作。但如果是浏览器环境,要从服务器加载模块,这是就必须采用异步模式。所以就有了 AMD CMD 等解决方案。

1
2
3
4
5
6
7
8
// a.js
var a = {
"a":a,
"b":b
}
module.export = a //模块导出
// b.js
var b = require('./a.js') //模块引入

ES6 Module

使用 export 定义了对外接口,其他文件通过 import 加载这个模块,export 命令可以出现在模块的任何位置,只要处于模块顶层就可以。
import 命令具有提升效果,会提升到整个模块的头部,首先执行。由于 import 是静态执行,所以不能使用表达式和变量,这些只有在运行时才能得到结果的语法结构。

1
2
3
4
export { foo, bar } from 'my_module';
export { foo as myFoo } from 'my_module';// 接口改名
export { es6 as default } from './someModule';
export { default as es6 } from './someModule';

ES6默认就是严格模式,严格模式主要有以下限制:

  • 变量必须声明后再使用
  • 函数的参数不能有同名属性,否则报错
  • 不能使用with语句
  • 不能对只读属性赋值,否则报错
  • 不能使用前缀 0 表示八进制数,否则报错
  • 不能删除不可删除的属性,否则报错
  • 不能删除变量 delete prop,会报错,只能删除属性 delete global[prop]
  • eval 不会在它的外层作用域引入变量
  • eval 和 arguments 不能被重新赋值
  • arguments 不会自动反映函数参数的变化
  • 不能使用 arguments.callee
  • 不能使用 arguments.caller
  • 禁止 this 指向全局对象
  • 不能使用 fn.caller和fn.arguments 获取函数调用的堆栈
  • 增加了保留字(比如protected、static和interface)

Module 的加载实现

1
2
3
<script type="module" src="./foo.js"></script>
<!-- 等同于 -->
<script type="module" src="./foo.js" defer></script>

  • 代码是在模块作用域之中运行,而不是在全局作用域运行。模块内部的顶层变量,外部不可见。
  • 模块脚本自动采用严格模式,不管有没有声明 use strict。
  • 模块之中,可以使用 import命令 加载其他模块(.js后缀不可省略,需要提供绝对 URL 或相对 URL),也可以使用 export 命令输出对外接口。
  • 模块之中,顶层的 this 关键字返回 undefined,而不是指向 window。也就是说,在模块顶层使用 this 关键字,是无意义的。
  • 同一个模块如果加载多次,将只执行一次。

ES6 模块与 CommonJS 模块的差异以及如何处理”循环加载”:

  • CommonJS 模块输出的是一个值的拷贝,ES6 模块输出的是值的引用。CommonJS 模块是运行时加载,ES6 模块是编译时输出接口。
  • ES6 模块是动态引用,如果使用 import 从一个模块加载变量(即import foo from ‘foo’),那些变量不会被缓存,而是成为一个指向被加载模块的引用,需要开发者自己保证,真正取值的时候能够取到值。CommonJS 模块代码在require的时候,就会全部执行。一旦出现某个模块被”循环加载”,就只输出已经执行的部分,还未执行的部分不会输出。

AMD 规范

异步加载模块,适用define方法定义模块,运行时核心思想是「Early Executing」,也就是提前执行依赖。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
// define
// id: 模块标识,可省。
// dependencies: 所依赖的模块,可省。
// factory: 模块的实现,或者一个JavaScript对象。

//a.js 只有factory
define(function() {
return {
mix: function(source, target) {
...
}
};
});

//b.js 依赖a.js
define(['a'], function(a) {
return {
show: function() {
...
}
}
});

//c.js 依赖a.js b.js
define(['a', 'b'], function(a, b) {
....
});

//d.js 对象模块
define({
data1: [],
data2: []
});

AMD规范允许输出模块兼容CommonJS规范,这时define方法如下:

1
2
3
4
5
6
7
define(function (require, exports, module) {
var reqModule = require("./a.js");
requModule.mix();
exports.asplode = function () {
...
}
});

CMD 规范

CMD 和 AMD 的区别:对于依赖的模块AMD是提前执行,CMD是延迟执行。不过RequireJS从2.0开始,也改成可以延迟执行(根据写法不同,处理方式不通过),CMD依赖就近,AMD依赖前置。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
//AMD写法
define(['./a','./b'], function (a, b) {
//依赖一开始就写好
a.mix();
b.show();
});

//CMD写法
define(function (requie, exports, module) {
//依赖可以就近书写
var a = require('./a');
a.mix();

if (...) {
var b = requie('./b');
b.show();
}
});

UMD 规范

应用 UMD 规范的 js 文件其实就是一个立即执行函数。函数有两个参数,第一个参数是当前运行时环境,第二个参数是模块的定义体。在执行UMD规范时,会优先判断是当前环境是否支持AMD环境,然后再检验是否支持CommonJS环境,否则认为当前环境为浏览器环境( window )。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
(function (root, factory) {
if (typeof define === 'function' && define.amd) {
// AMD
define(['jquery'], factory);
} else if (typeof exports === 'object') {
// Node, CommonJS-like
module.exports = factory(require('jquery'));
} else {
// Browser globals (root is window)
root.returnExports = factory(root.jQuery);
}
}(this, function ($) {
// methods
function myFunc(){};
// exposed public method
return myFunc;
}));

Reference:
https://segmentfault.com/a/1190000006232697
http://www.cnblogs.com/imwtr/p/4666181.html
http://caibaojian.com/es6/module.html
http://www.cnblogs.com/vs1435/p/6553139.html