前端为什么会出现模块化这个概念,主要原因是随着前端项目的复杂性和项目团队规模的扩大,命名冲突和文件依赖成为影响前端开发效率的两个重要问题。模块化规范的另一个目的是提高JS程序的可移植性。
CommonJS
官方JavaScript规范定义的API主要是为了构建基于浏览器端的应用程序。在服务器端,并没有官方的API定义。
CommonJS定义了许多普通应用程序(主要指非浏览器应用)使用的API,它的终极目标是提供一个类似Python、Ruby和Java的标准库,从而开发者可以使用CommonJS API编写可以运行在不同JS解释器和不同主机环境的应用程序。
-
NodeJS和CommonJS之间的关系
CommonJS是一种规范,NodeJS是这种规范的实现。JavaScript作为本地变成语言这种特殊应用程序,也是随着NodeJS的火热而进入大众的视野。
-
浏览器加载 CommonJS 模块的原理与实现
npm的模块都是JS语言写的,但是浏览器用不了,因为浏览器不支持CommonJS格式。要想让浏览器用上这些模块,必须转换格式。
浏览器不兼容CommonJS的根本原因,在于缺少四个Node.js环境的变量。
module exports require global
只要能够提供这四个变量,浏览器就能加载CommonJS模块。
-
Browserify的实现
知道了原理,就能做出工具了。Browserify(require(‘modules’) in the browser)是目前最常用的CommonJS格式转换的工具。
举例: main.js模块加载foo.js模块
// foo.js
module.exports = function(x) {
console.log(x);
};
// main.js
var foo = require("./foo");
foo("Hi");
使用下面的命令,就能将main.js转为浏览器可用的格式。
$ browserify main.js > compiled.js
Browserify到底做了什么?安装一下browser-unpack,就能看清楚了。
$ npm install browser-unpack -g
然后,将前面生成的compile.js解包。
$ browser-unpack < compiled.js
[
{
"id":1,
"source":"module.exports = function(x) {\n console.log(x);\n};",
"deps":{}
},
{
"id":2,
"source":"var foo = require(\"./foo\");\nfoo(\"Hi\");",
"deps":{"./foo":1},
"entry":true
}
]
可以看到,browerify将所有模块放入一个数组,id属性是模块的编号,source属性是模块的源码,deps属性是模块的依赖。
因为main.js里面加载了foo.js,所以deps属性就制定./foo对应的1号模块。执行的时候,浏览器遇到require(‘./foo’)语句,就自动执行1号模块的source属性,并就爱那个执行后的module.exports属性值输出。
AMD
AMD(异步模块定,Asynchronous Module Definition)格式总体的目标是为现在的开发者提供一个可用的模块化JS解决方案。
CommonJS通过引入模块化使得在服务器端编写JS应用成为可能,但是由于一个重大的局限,使得CommonJS规范不适用与浏览器环境。举例如下。
var math = require('math');
math.add(2, 3); 第二行math.add(2, 3),在第一行require('math')之后运行,因此必须等math.js加载完成。也就是说,如果加载时间很长,整个应用就会停在那里等。
这对服务器端不是一个问题,因为所有的模块都存放在本地硬盘,可以同步加载完成,等待时间就是硬盘的读取时间。但是,对于浏览器,这确实一个大问题,因为模块都放在服务器端,等待时间取决于网速的快慢,可能要等很长时间,浏览器处于“假死”状态。
因此,浏览器端的模块,不能采用“同步加载”(synchronous),只能采用“异步加载”(asynchronous)。这就是AMD规范诞生的背景。
AMD是“Asynchronous Module Definition”的缩写,意思就是“异步模块定义”。它采用异步方式加载模块,模块的加载不影响它后面语句的运行。所有依赖这个模块的语句,都定义在一个回调函数中,等到加载完成之后,这个回调函数才会运行。
AMD也采用require()语句加载模块,但是不同于CommonJS,他要求两个参数:
require([module], callback);
第一个参数[module],是一个数组,里面的成员就是要加载的模块;第二个参数callback,则是加载成功之后的回调函数。
CMD
CMD(Common Module Definition)模块定义规范,是@玉伯提出的一个类似于AMD的规范,该规范明确了模块的基本书写格式和基本交互规则。
AMD和CMD的区别
下面来看下@玉伯给出的官方解释。
AMD和CMD等规范的目的都是为了JS的模块化开发,特别是在浏览器端的。目前这些规范的实现都能达成浏览器端模块化开发的目的。
区别在于:
- 对于依赖的模块,AMD是提前执行,CMD是延迟执行。不过RequireJS从2.0开始,也改成了可以延迟执行(根据写法不同,处理方式不同)。CMD推崇as lazy as possible。
-
CMD推崇依赖就近,AMD推崇依赖前置。如下:
// CMD define(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官方文档里默认上面的模块定义写法。
- AMD的API默认是一个当多个用,CMD的API严格区分,推崇职责单一。比如AMD里,require分全局require和局部require,都叫require。CMD里,没有全局require,而是根据模块系统的完备性,提供seajs.use来实现模块系统的加载启动。CMD里,每个API都简单纯粹。