前端模块化规范

前端模块化规范

通常来说,一个文件就是一个模块,有自己的作用域,只向外暴露特定的变量和函数。目前流行的js模块化规范有CommonJS、AMD、CMD、UMD以及ES6的模块系统。

AMD / CMD / CommonJs 是JS模块化开发的标准规范,目前对应的实现是

RequireJs / SeaJs / nodeJs

简介

CommonJS

CommonJS用同步的方式加载模块。在服务端,模块文件都存放在本地磁盘,读取非常快,所以这样做不会有问题。但是在浏览器端,限于网络原因,更合理的方案是使用异步加载。

NodeJS 是 CommonJS 规范的主要实践者

有4个重要的环境变量为模块化实现提供支持

module

exports

1
2
3
4
5
6
7
8
9
10
// 定义模块math.js
var total = 10;
function add(a, b) {
return a + b;
}
// 需要向外暴露的函数、变量
module.exports = {
add: add,
total: total
}

require

1
2
3
4
5
6
7
8
/** 必须加./路径,不加的话只会去node_modules文件找 **/
// 引用自定义的模块时,参数包含路径,可省略.js
var math = require('./math');
math.add(2, 5);

// 引用核心模块时,不需要带路径
var http = require('http');
http.createService(...).listen(3000);

global

AMD

浏览器优先 , 异步加载

预加载

所有依赖这个模块的语句,都定义在一个回调函数中,等到加载完成之后,这个回调函数才会运行。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
/** 网页中引入require.js及main.js **/
<script src="js/require.js" data-main="js/main"></script>

/** main.js 入口文件/主模块 **/
// 首先用config()指定各模块路径和引用名
require.config({
baseUrl: "js/lib",
paths: {
"jquery": "jquery.min", //实际路径为js/lib/jquery.min.js
"underscore": "underscore.min",
}
});
// 执行基本操作
require(["jquery","underscore"],function($,_){
// some code here
});
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
// 定义math.js模块
define(function () {
var basicNum = 0;
var add = function (x, y) {
return x + y;
};
return {
add: add,
basicNum :basicNum
};
});

// 定义一个依赖underscore.js的模块
define(['underscore'],function(_){
var classify = function(list){
_.countBy(list,function(num){
return num > 30 ? 'old' : 'young';
})
};
return {
classify :classify
};
})

// 引用模块,将模块放在[]内
require(['jquery', 'math'],function($, math){
var sum = math.add(10,20);
$("#sum").html(sum);
});

CMD

异步加载

懒加载

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
/** AMD写法 **/
define(["a", "b", "c", "d", "e", "f"], function(a, b, c, d, e, f) {
// 等于在最前面声明并初始化了要用到的所有模块
a.doSomething();
if (false) {
// 即便没用到某个模块 b,但 b 还是提前执行了
b.doSomething()
}
});

/** CMD写法 **/
define(function(require, exports, module) {
var a = require('./a'); //在需要时申明
a.doSomething();
if (false) {
var b = require('./b');
b.doSomething();
}
});

/** sea.js **/
// 定义模块 math.js
define(function(require, exports, module) {
var $ = require('jquery.js');
var add = function(a,b){
return a+b;
}
exports.add = add;
});

// 加载模块
seajs.use(['math.js'], function(math){
var sum = math.add(1+2);
});

UMD

UMD是AMD和CommonJS的一个糅合。AMD是浏览器优先,异步加载;CommonJS是服务器优先,同步加载。

先判断是否支持node.js的模块,存在就使用node.js;再判断是否支持AMD(define是否存在),存在则使用AMD的方式加载。这就是所谓的UMD

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
((root, factory) => {
if (typeof define === 'function' && define.amd) {
//AMD
define(['jquery'], factory);
} else if (typeof exports === 'object') {
//CommonJS
var $ = requie('jquery');
module.exports = factory($);
} else {
//都不是,浏览器全局定义
root.testModule = factory(root.jQuery);
}
})(this, ($) => {
//do something... 这里是真正的函数体
});

ES6 Module

模块功能主要由两个命令构成:

export 规定模块的对外接口

import 输入其他模块提供的功能

特征

  • 严格模式:ES6 的模块自动采用严格模式
  • import read-only特性: import的属性是只读的,不能赋值,类似于const的特性
  • export/import提升: import/export必须位于模块顶级,不能位于作用域内;其次对于模块内的import/export会提升到模块顶部,这是在编译阶段完成的

总结/区别

CommonJs主要针对服务端

服务器端一般采用 同步加载 文件,也就是说需要某个模块,服务器端便停下来,等待它加载再执行。

AMD/CMD主要针对浏览器端

而浏览器端要保证效率,需要采用 异步加载 ,这就需要一个预处理,提前将所需要的模块文件并行加载好。

AMD/CMD 都是 并行加载 js文件

AMD预加载,在并行加载js文件同时,还会解析执行该模块(因为还需要执行,所以在加载某个模块前,这个模块的依赖模块需要先加载完成)

加载快速,尤其遇到多个大文件,因为并行解析,所以同一时间可以解析多个文件

加载顺序不一定,可能会为程序埋下大坑

CMD懒加载,虽然会一开始就并行加载js文件,但是不会执行,而是在需要的时候才执行。

每个JS文件的执行顺序在代码中是有体现的,是可控的

多文件加载速度叠加

JS是单线程,所有JS文件执行时间叠加在AMD和CMD中是一样的。

但CMD是懒加载,没法利用空闲时间,而AMD是文件加载好就执行,往往可以利用一些空闲时间。

参考

前端模块化——彻底搞懂AMD、CMD、UMD、ESM和CommonJS - 知乎

【新手入门 最精炼总结】AMD/CMD/CommonJs到底是什么?它们有什么区别?_amd与cmd-CSDN博客