AMD,CMD,CommonJS是目前最常用的三种模块化书写规范
CommonJS
- CommonJS规范是诞生比较早的。NodeJS就采用了
- 根据CommonJS规范,一个单独的文件就是一个模块。加载模块使用require方法,该方法读取一个文件并执行,最后返回文件内部的exports对象
- CommonJS
加载模块是同步的,所以只有加载完成才能执行后面的操作。像Node.js主要用于服务器的编程,加载的模块文件一般都已经存在本地硬盘,所以加载起来比较快,不用考虑异步加载的方式,所以CommonJS规范比较适用
CommonJS。是这样加载模块:
/ foobar.js
//私有变量
var test = 123;
//公有方法
function foobar () {
this.foo = function () {
// do someing ...
}
this.bar = function () {
//do someing ...
}
}
//exports对象上的方法和变量是公有的
var foobar = new foobar();
exports.foobar = foobar;
//require方法默认读取js文件,所以可以省略js后缀
var test = require('./foobar').foobar;
test.bar();
或
var clock = require('clock');
clock.start();
这种写法适合服务端,因为在服务器读取模块都是在本地磁盘,加载速度很快。但是如果在客户端,加载模块的时候有可能出现“假死”状况。比如上面的例子中clock的调用必须等待clock.js请求成功,加载完毕。那么,能不能异步加载模块呢
AMD
- AMD,即 (Asynchronous Module Definition),这种规范是异步的加载模块,requireJs应用了这一规范。
- AMD 标准:
https://github.com/amdjs/amdjs-api/blob/master/AMD.md - 先定义所有依赖,然后在加载完成后的回调函数中执行:
- AMD异步加载模块。它的模块支持对象 函数 构造器 字符串 JSON等各种类型的模块
适用AMD规范适用define方法定义模块
//通过数组引入依赖 ,回调函数通过形参传入依赖
define(['someModule1', ‘someModule2’], function (someModule1, someModule2) {
function foo () {
/// someing
someModule1.test();
}
return {foo: foo}
});
AMD规范允许输出模块兼容CommonJS规范,这时define方法如下:
define(function (require, exports, module) {
var reqModule = require("./someModule");
requModule.test();
exports.asplode = function () {
//someing
}
});
或
require([module], callback);
用AMD写上一个模块
require(['clock'],function(clock){
clock.start();
});
AMD虽然实现了异步加载,但是开始就把所有依赖写出来是不符合书写的逻辑顺序的,能不能像commonJS那样用的时候再require,而且还支持异步加载后再执行呢
CMD
CMD (Common Module Definition), 是seajs推崇的规范,CMD则是依赖就近,用的时候再require。它写起来是这样的
CMD 标准: https://github.com/cmdjs/specification/blob/master/draft/module.md
//AMD
define(['./a','./b'], function (a, b) {
//依赖一开始就写好
a.test();
b.test();
});
//CMD
define(function (requie, exports, module) {
//依赖可以就近书写
var a = require('./a');
a.test();
...
//软依赖
if (status) {
var b = requie('./b');
b.test();
}
});
define(function(require, exports, module) {
var clock = require('clock');
clock.start();
});
3.AMD和CMD最大的区别
- 是对依赖模块的执行时机处理不同,而不是加载的时机或者方式不同,二者皆为异步加载模块
- -可以很明显的看出RequireJS的做法是并行加载所有依赖的模块, 并完成解析后, 再开始执行其他代码, 因此执行结果只会"停顿"1次, 完成整个过程是会比SeaJS要快
- 而SeaJS一样是并行加载所有依赖的模块, 但不会立即执行模块, 等到真正需要(require)的时候才开始解析, 这里耗费了时间,
因为这个特例中的模块巨大, 因此造成"停顿"2次的现象, 这就是我所说的SeaJS中的"懒执行" - AMD依赖前置,js可以方便知道依赖模块是谁,立即加载;而CMD就近依赖,需要使用把模块变为字符串解析一遍才知道依赖了那些模块,这也是很多人诟病CMD的一点,牺牲性能来带来开发的便利性,实际上解析模块用的时间短到可以忽略
- seajs 与 requirejs 在模块的加载方面是没有差异的,无论是 requirejs 在定义模块时定义的依赖模块,还是 seajs
在 factory 函数中 require 的依赖模块,在会在加载当前模块时被载入,异步,并且顺序不可控。差异在于 factory
函数执行的时机 - AMD的api默认是一个当多个用,CMD严格的区分推崇职责单一。例如:AMD里require分全局的和局部的。CMD里面没有全局的
require,提供 seajs.use()来实现模块系统的加载启动。CMD里每个API都简单纯粹 - 需要提一下的是二者对待依赖模块的加载是一致的,在factory执行时,依赖模块都已被加载。从代码上来看,AMD中在BEGIN处a、b的factory都是执行过的;而CMD中虽然a、b模块在BEGIN已被加载,但尚未执行,需要调用require执行依赖模块。这就是CMD中着重强调的延迟执行
- AMD与CMD都借鉴了CommonJs,宏观层面必有一致性,比如整体处理流程:
模块的加载解析到执行过程一共经历了6个步骤:
1、由入口进入程序
2、进入程序后首先要做的就是建立一个模块仓库(这是防止重复加载模块的关键),JavaScript原生的object对象最为适合,key代表模块Id,value代表各个模块,处理主模块
3、向模块仓库注册一模块,一个模块最少包含四个属性:id(唯一标识符)、deps(依赖项的id数组)、factory(模块自身代码)、status(模块的状态:未加载、已加载未执行、已执行等),放到代码中当然还是object最合适
4、模块即是JavaScript文件,使用无阻塞方式(动态创建script标签)加载模块
scriptElement= document.createElement('script');
scriptElement.src=moduleUrl;
scriptElement.async=true;
scriptElement.onload=function(){.........};
document.head.appendChild(scriptElement);
5、模块加载完毕后,获取依赖项(amd、cmd区别),改变模块status,由statuschange后,检测所有模块的依赖项
由于requirejs与seajs遵循规范不同,requirejs在define函数中可以很容易获得当前模块依赖项。而seajs中不需要依赖声明,所以必须做一些特殊处理才能否获得依赖项。方法将factory作toString处理,然后用正则匹配出其中的依赖项,比如出现require(./a),则检测到需要依赖a模块。
同时满足非阻塞和顺序执行就需要需要对代码进行一些预处理,这是由于CMD规范和浏览器环境特点所决定的
6、如果模块的依赖项完全加载完毕(amd中需要执行完毕,cmd中只需要文件加载完毕,注意这时候的factory尚未执行,当使用require请求该模块时,factory才会执行,所以在性能上seajs逊于requirejs),执行主模块的factory函数;否则进入步骤3