JS模块化

JS的模块通常就是指一个文件,每一个文件当做一个独立的作用域,对外暴露变量或者函数。
JS模块化有几种方案分别是 CommonJS,AMD,CMD,UMD,ESModule

CommonJS

适用于服务端
最大的特点就是同步加载
流程:
路径解析–>文件加载–>模块封装–>编译执行–>缓存

在浏览器端使用commonJS具体实现:

  1. 先使用require.register注册文件路径和对应方法之间的映射关系保存在require.modules中
  2. 再使用require方法,通过传入的路径去require.modules中取出对应的方法
  3. 使用require获取方法的同时,会触发依赖模块中的require方法,这样就实现了模块的加载
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
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
function require(path) {
let mod = require.modules[path];
mod.exports = {};
mod.call(window, require, mod, mod.exports);
return mod.exports;
}

require.modules = {};

require.register = function (path, fn) {
// 异步加载
require.modules[path] = fn;
}

// 1. 异步加载的逻辑也不复杂,在同步加载的基础上增加require.ensure方法,预先在modules对象上挂在onload方法
// 2. 修改require.register方法,增加异步模块注册逻辑,在异步模块注册完成后触发onload,以达到模块异步加载的需求

// 异步的实现
function require(path) {
let mod = require.modules[path].method;
mod.exports = {};
mod.call(window, require, mod, mod.exports);
return mod.exports;
}

require.modules = {};

require.register = function (path, fn) {
// 异步加载
if (require.modules[path] && require.modules[path].status === 'loading') {
// 异步加载成功
require.modules[path].status = 'loaded'
require.modules[path].method = fn;
require.modules[path].onload(require(path));
} else {
require.modules[path] = {
moduleName: path, // 模块Id
status: 'loaded',
onload: null,
method: fn
};
}
}

require.ensure = function (path, cb) {
require.modules[path] = {
moduleName: path, // 模块Id
status: 'loading',
onload: cb,
method: null
};
var head = document.querySelector('head')
var script = document.createElement('script');
script.async = true;
script.src = path;
setTimeout(() => {
head.appendChild(script);
},5000 );
}

AMD

AMD 规范加载模块是异步的,并允许函数回调,不必等到所有模块都加载完成,后续操作可以正常执行。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
//  规范 API
define(id?, dependencies?, factory);
define.amd = {};

// 定义无依赖的模块
define({
add: function(x,y){
return x + y;
}
});

// 定义有依赖的模块
define(["./a.js"], function(alpha){
return {
verb: function(){
return 1;
}
}
});

RequireJS

CMD

CMD中,模块作为依赖且被引用时才会初始化,否则只会加载。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
//CMD
define(function (requie, exports, module) {

//依赖可以就近书写
var a = require('./a');
a.test();

...
//软依赖
if (status) {
var b = requie('./b');
b.test();
}
});

SeaJS

AMD,CMD区别

  1. AMD推崇依赖前置,在定义模块的时候就要声明其依赖的模块并加载
  2. CMD推崇就近依赖,只有在用到某个模块的时候再去require

UMD兼容写法

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
// a.js
(function (global, factory) {
if (typeof exports === 'object' && typeof module !== 'undefined') {
// CommonJS、CMD规范检查
module.exports = factory();
} else if (typeof define === 'function' && define.amd) {
// AMD规范检查
define(factory);
} else {
// 浏览器注册全局对象
global.A = factory();
}
})(this, (function () {
function say() {
console.log('hello world');
}

return {
say: say
}
}))

// 使用

<script src="a.js"></script>
<script>
A.say(); // hello world
</script>

const A = require('./a.js');
A.say();

ES6

commonJS与ES6module区别

  1. CommonJS是运行时加载,ES6时编译时输出接口
    commonJS加载的是一个对象,该对象在脚本运行完才会产生,es6模块不是模块,,他的对外接口只是一种静态定义,在代码解析执行就会生成。
    因此es6有两个特点,一是import会被提前到文件顶部,exports会有变量提升的效果
  2. CommonJS是值的拷贝,ES6是值的引用

commonJS源码解析
参考文章