js模块化之CommonJS、AMD、CMD、ES6
模块化是一种将系统分离成独立功能部分的方法,严格定义模块接口、模块间具有透明性。
Web应用越来越复杂,简单的代码组织方式已经无法满足业务和架构需求,需要通过模块化来组织代码。
CommonJS
这里的CommonJS规范指的是CommonJS Modules/1.0规范。
CommonJS是一个更偏向于服务器端的规范。NodeJS采用了这个规范。CommonJS的一个模块就是一个脚本文件。require命令第一次加载该脚本时就会执行整个脚本,然后在内存中生成一个对象。
{
id: '...',
exports: { ... },
loaded: true,
...
}
id是模块名,exports是该模块导出的接口,loaded表示模块是否加载完毕。此外还有很多属性,这里省略了。
以后需要用到这个模块时,就会到exports属性上取值。即使再次执行require命令,也不会再次执行该模块,而是到缓存中取值。
// math.js
exports.add = function(a, b) {
return a + b;
}
var math = require('math');
math.add(2, 3); // 5
服务器端JS | 浏览器端JS |
相同的代码需要多次执行 | 代码需要从一个服务器端分发到多个客户端执行 |
CPU和内存资源是瓶颈 | 带宽是瓶颈 |
加载时从磁盘中加载 | 加载时需要通过网络加载 |
由于CommonJS是同步加载模块,这对于服务器端不是一个问题,因为所有的模块都放在本地硬盘。等待模块时间就是硬盘读取文件时间,很小。但是,对于浏览器而言,它需要从服务器加载模块,涉及到网速,代理等原因,一旦等待时间过长,浏览器处于”假死”状态。
所以在浏览器端,不适合于CommonJS规范。所以在浏览器端又出现了一个规范—-AMD。
AMD
CommonJS解决了模块化的问题,但这种同步加载方式并不适合于浏览器端。
AMD是”Asynchronous Module Definition”的缩写,即”异步模块定义”。它采用异步方式加载模块,模块的加载不影响它后面语句的运行。
AMD也采用require命令加载模块,但是不同于CommonJS,它要求两个参数:
require([module], callback);
第一个参数[module],是一个数组,里面的成员是要加载的模块,callback是加载完成后的回调函数。如果将上述的代码改成AMD方式:
require(['math'], function(math) {
math.add(2, 3);
})
其中,回调函数中参数对应数组中的成员(模块)。
requireJS加载模块,采用的是AMD规范。也就是说,模块必须按照AMD规定的方式来写。
具体来说,就是模块书写必须使用特定的define()函数来定义。如果一个模块不依赖其他模块,那么可以直接写在define()函数之中。
define(id?, dependencies?, factory);
id:模块的名字,如果没有提供该参数,模块的名字应该默认为模块加载器请求的指定脚本的名字;
dependencies:模块的依赖,已被模块定义的模块标识的数组字面量。依赖参数是可选的,如果忽略此参数,它应该默认为
["require", "exports", "module"]
。然而,如果工厂方法的长度属性小于3,加载器会选择以函数的长度属性指定的参数个数调用工厂方法。factory:模块的工厂函数,模块初始化要执行的函数或对象。如果为函数,它应该只被执行一次。如果是对象,此对象应该为模块的输出值。
假定现在有一个math.js文件,定义了一个math模块。那么,math.js书写方式如下:
// math.js
define(function() {
var add = function(x, y) {
return x + y;
}
return {
add: add
}
})
加载方法如下:
// main.js
require(['math'], function(math) {
alert(math.add(1, 1));
})
如果math模块还依赖其他模块,写法如下:
// math.js
define(['dependenceModule'], function(dependenceModule) {
// ...
})
当require()函数加载math模块的时候,就会先加载dependenceModule模块。当有多个依赖时,就将所有的依赖都写在define()函数第一个参数数组中,所以说AMD是依赖前置的。这不同于CMD规范,它是依赖就近的。
CMD
CMD推崇依赖就近,延迟执行。可以把你的依赖写进代码的任意一行,如下:
define(factory)
factory
为函数时,表示是模块的构造方法。执行该构造方法,可以得到模块向外提供的接口。factory 方法在执行时,默认会传入三个参数:require、exports 和 module.
// CMD
define(function(require, exports, module) {
var a = require('./a');
a.doSomething();
var b = require('./b');
b.doSomething();
})
如果使用AMD写法,如下:
// AMD
define(['a', 'b'], function(a, b) {
a.doSomething();
b.doSomething();
})
这个规范实际上是为了Seajs的推广然后搞出来的。那么看看SeaJS是怎么回事儿吧,基本就是知道这个规范了。
同样Seajs也是预加载依赖js跟AMD的规范在预加载这一点上是相同的,明显不同的地方是调用,和声明依赖的地方。AMD和CMD都是用difine和require,但是CMD标准倾向于在使用过程中提出依赖,就是不管代码写到哪突然发现需要依赖另一个模块,那就在当前代码用require引入就可以了,规范会帮你搞定预加载,你随便写就可以了。但是AMD标准让你必须提前在头部依赖参数部分写好(没有写好? 倒回去写好咯)。这就是最明显的区别。
sea.js通过sea.use()
来加载模块。
seajs.use(id, callback?)
UMD
由于CommonJS是服务器端的规范,跟AMD、CMD两个标准实际不冲突。
当我们写一个文件需要兼容不同的加载规范的时候怎么办呢,看看下面的代码。
(function (root, factory) {
if (typeof define === 'function' && define.amd) {
// AMD
define(['jquery', 'underscore'], factory);
} else if (typeof exports === 'object') {
// Node, CommonJS之类的
module.exports = factory(require('jquery'), require('underscore'));
} else {
// 浏览器全局变量(root 即 window)
root.returnExports = factory(root.jQuery, root._);
}
}(this, function ($, _) {
// 方法
function a(){}; // 私有方法,因为它没被返回 (见下面)
function b(){}; // 公共方法,因为被返回了
function c(){}; // 公共方法,因为被返回了
// 暴露公共方法
return {
b: b,
c: c
}
}));
这个代码可以兼容各种加载规范了。
ES6
es6通过import
、export
实现模块的输入输出。其中import命令用于输入其他模块提供的功能,export命令用于规定模块的对外接口。
export
一个模块就是一个独立的文件。该文件内部的所有变量,外部无法获取。如果希望外部文件能读取模块内部的某个变量,就必须使用export关键字输出该变量。如:
// profile.js
export var a = 1;
export var b = 2;
export var c = 3;
里面export命令对外部输出了三个变量。
下面的写法是等价的,这种方式更加清晰(在脚本底部一眼就能看清楚输出了哪些变量):
var a = 1;
var b = 2;
var c = 3;
export {a, b, c}
export命令除了输出变量,还可以输出函数或类(class)。
- 输出函数
export function foo(){}
function foo(){}
function bar(){}
export {foo, bar as bar2}
其中上面的as表示给输出的变量重命名。
要注意的是,export输出的变量只能位于文件的顶层,如果处于块级作用域内,会报错。如:
function foo() {
export 'bar'; // SyntaxError
}
- 输出类
export default class {} // 关于default下面会说
export语句输出的接口,与其对应对值是动态绑定关系,即通过该接口,可以取到模块内部实时的值。// foo.js
export var foo = 'foo';
setTimeout(function() {
foo = 'foo2';
}, 500);
// main.js
import * as m from './foo';
console.log(m.foo); // foo
setTimeout(() => console.log(m.foo), 500); // foo2
这一点与CommonJS规范完全不同。CommonJS模块输出的是值的缓存,不存在动态更新。
import
import命令可以导入其他模块通过export导出的部分。
// abc.js
var a = 1;
var b = 2;
var c = 3;
export {a, b, c}
//main.js
import {a, b, c} from './abc';
console.log(a, b, c);
import后面的from指定模块文件的位置,.js后缀可以省略。
如果想为输入的变量重新取一个名字,使用as关键字(也可以在输出中使用)。
import {a as aa, b, c};
console.log(aa, b, c)
模块的整体加载
用星号(*)指定一个对象,所有输出值都加载在这个对象上面。
// main.js
import { area, circumference } from './circle';
console.log('圆面积:' + area(4));
console.log('圆周长:' + circumference(14));
import * as circle from './circle';
console.log('圆面积:' + circle.area(4));
console.log('圆周长:' + circle.circumference(14));
export default
为了给用户提供方便,让他们不用阅读文档就能加载模块,就要用到export default
命令,为模块指定默认输出。
// export-default.js
export default function () {
console.log('foo');
}
上面代码是一个模块文件export-default.js
,它的默认输出是一个函数。
其他模块加载该模块时,import
命令可以为该匿名函数指定任意名字。
// import-default.js
import customName from './export-default';
customName(); // 'foo'
上面代码的import
命令,可以用任意名称指向export-default.js
输出的方法,这时就不需要知道原模块输出的函数名。需要注意的是,这时import
命令后面,不使用大括号。
export default
命令用于指定模块的默认输出。显然,一个模块只能有一个默认输出,因此export default
命令只能使用一次。所以,import命令后面才不用加大括号,因为只可能唯一对应export default
命令。
本质上,export default
就是输出一个叫做default
的变量或方法,然后系统允许你为它取任意名字。
它后面不能跟变量声明语句。
// 正确
export var a = 1;
// 正确
var a = 1;
export default a;
// 错误
export default var a = 1;
// modules.js
function add(x, y) {
return x * y;
}
export {add as default};
// 等同于
// export default add;
// app.js
import { default as foo } from 'modules';
// 等同于
// import foo from 'modules';
参考:
javascript模块化之CommonJS、AMD、CMD、UMD、ES6
上一篇: 芥末有什么功效与作用,下面就来看看把
下一篇: 卢伟冰过生日:用红米Note 8吹灭蜡烛
推荐阅读
-
再次梳理AMD、CMD、CommonJS、ES6 Module的区别
-
JavaScript模块化-CommonJS、AMD、CMD、UMD、ES6
-
前端模块化:CommonJS,CMD,AMD,ES6
-
一览js模块化:从CommonJS到ES6
-
详解搞清CommonJS、AMD、CMD、ES6的联系与区别
-
前端模块化小总结—commonJs,AMD,CMD, ES6 的Module
-
javascript模块化之CommonJS、AMD、CMD、UMD、ES6
-
javascript模块化之CommonJS、AMD、CMD、UMD、ES6
-
JavaScript里面的模块化4种方式(CommonJS,AMD,CMD,es6Module)
-
模块化module/CommonJS/Browserify/AMD/RequireJS/CMD/SeaJS/ES6_Babel