为什么要使用模块化?
-
最主要的目的:
- 解决命名冲突
- 依赖管理
-
其他价值
- 提高代码可读性
- 代码解耦,提高复用性
CMD、AMD、CommonJS 规范分别指什么?有哪些应用
这三个规范都是为javascript模块化加载而生的,都是在用到或者预计要用到某些模块时候加载该模块,使得大量的系统巨大的庞杂的代码得以很好的组织和管理。模块化使得我们在使用和管理代码的时候不那么混乱,而且也方便了多人的合作。
CMD规范
- CMD(Common Module Definition)通用模板定义,它是在一个浏览器端模块化的开发规范
- 使用CMD规范进行开发需要使用SeaJS
- Sea.js推荐一个模块为一个文件
代码的书写格式:
define(id?,dependencies?,factory);
- id:(可不写,默认文件名),用来定义模块的标识,通用文件名作为模块ID
- dependencies:(可不写)一个当前模块依赖的模块名称数组(因为CMD推崇依赖就近,因此一般不在此处指定)
- factory:
- function(require,exports,module)
- require(id):require是一个方法,接受模块标识作为唯一参数,用来获取其他模块提供的接口
- exports是一个对象,用来向外提供模块接口
- module是一个对象,上面储存了与当前模块相关联的一些属性和方法
define Function
define是一个全局函数,用来定义模块
define接受factory参数,factory可以是一个函数,也可以是一个对象或字符串
factory为对象、字符串时,表示模块的接口就是该对象、字符串。例如定义一个JSON数据模块:
define({ “foo”:“bar” });
factory为函数时,表示模块的构造方法。执行该构造方法,可以得到模块向外提供的接口。factory方法在执行时,默认传入三个参数:require、exports、module:
define(function(require,exports,module){
})
define define(id?, deps?, factory)
define 也可以接受两个以上参数。字符串 id 表示模块标识,数组 deps 是模块依赖。比如:
define('hello', ['jquery'], function(require, exports, module) {
});
id和 deps参数可以省略。省略时,可以通过构建工具自动生成。
注意:带 id和 deps参数的 define用法不属于 CMD 规范,而属于 Modules/Transport 规范。
define.cmd Object
一个空对象,可用来判定当前页面是否有 CMD 模块加载器:
if (typeof define === "function" && define.cmd) {
// 有 Sea.js 等 CMD 模块加载器存在
}
require Function
require是factory函数的第一个参数
require是一个方法,接受 模块标识 作为唯一参数,用来获取其他模块提供的接口。
define(function(require, exports) {
// 获取模块 a 的接口
var a = require('./a');
// 调用模块 a 的方法
a.doSomething();
});
require.async require.async(id, callback?)
require.async 方法用来在模块内部异步加载模块,并在加载完成后执行指定回调。callback 参数可选。
define(function(require, exports, module) {
// 异步加载一个模块,在加载完成时,执行回调
require.async('./b', function(b) {
b.doSomething();
});
// 异步加载多个模块,在加载完成时,执行回调
require.async(['./c', './d'], function(c, d) {
c.doSomething();
d.doSomething();
});
});
注意:require 是同步往下执行,require.async 则是异步回调执行。require.async 一般用来加载可延迟异步加载的模块。
require.resolve require.resolve(id)
使用模块系统内部的路径解析机制来解析并返回模块路径。该函数不会加载模块,只返回解析后的绝对路径。
define(function(require, exports) {
console.log(require.resolve('./b'));
// ==> http://example.com/path/to/b.js
});
这可以用来获取模块路径,一般用在插件环境或需动态拼接模块路径的场景下。
exports Object
exports 是一个对象,用来向外提供模块接口。
define(function(require, exports) {
// 对外提供 foo 属性
exports.foo = 'bar';
// 对外提供 doSomething 方法
exports.doSomething = function() {};
});
除了给 exports 对象增加成员,还可以使用 return 直接向外提供接口。
define(function(require) {
// 通过 return 直接提供接口
return {
foo: 'bar',
doSomething: function() {}
};
});
如果 return 语句是模块中的唯一代码,还可简化为:
define({
foo: 'bar',
doSomething: function() {}
});
上面这种格式特别适合定义 JSONP 模块。
特别注意:下面这种写法是错误的!
define(function(require, exports) {
// 错误用法!!!
exports = {
foo: 'bar',
doSomething: function() {}
};
});
正确的写法是用 return 或者给 module.exports 赋值:
define(function(require, exports, module) {
// 正确写法
module.exports = {
foo: 'bar',
doSomething: function() {}
};
});
提示:exports 仅仅是 module.exports 的一个引用。在 factory 内部给 exports 重新赋值时,并不会改变 module.exports 的值。因此给 exports 赋值是无效的,不能用来更改模块接口。
module Object
module 是一个对象,上面存储了与当前模块相关联的一些属性和方法。
module.id String
模块的唯一标识
define('id', [], function(require, exports, module) {
// 模块代码
});
上面代码中,define 的第一个参数就是模块标识。
module.uri String
根据模块系统的路径解析规则得到的模块绝对路径。
define(function(require, exports, module) {
console.log(module.uri);
// ==> http://example.com/path/to/this/file.js
});
一般情况下(没有在 define 中手写 id 参数时),module.id 的值就是 module.uri,两者完全相同。
module.dependencies Array
dependencies 是一个数组,表示当前模块的依赖。
module.exports Object
当前模块对外提供的接口。
传给 factory 构造方法的 exports 参数是 module.exports 对象的一个引用。只通过 exports 参数来提供接口,有时无法满足开发者的所有需求。 比如当模块的接口是某个类的实例时,需要通过 module.exports 来实现:
define(function(require, exports, module) {
// exports 是 module.exports 的一个引用
console.log(module.exports === exports); // true
// 重新给 module.exports 赋值
module.exports = new SomeClass();
// exports 不再等于 module.exports
console.log(module.exports === exports); // false
});
注意:对 module.exports 的赋值需要同步执行,不能放在回调函数里。下面这样是不行的:
// x.js
define(function(require, exports, module) {
// 错误用法
setTimeout(function() {
module.exports = { a: "hello" };
}, 0);
});
在 y.js 里有调用到上面的 x.js:
// y.js
define(function(require, exports, module) {
var x = require('./x');
// 无法立刻得到模块 x 的属性 a
console.log(x.a); // undefined
});
小结
经常使用的 API 只有 define, require, require.async, exports, module.exports 这五个。其他 API 有个印象就好。
与 RequireJS 的 AMD 规范相比,CMD 规范尽量保持简单,并与 CommonJS 和 Node.js 的 Modules 规范保持了很大的兼容性。通过 CMD 规范书写的模块,可以很容易在 Node.js 中运行。
AMD规范
- AMD (Asynchronous Module Definition, 异步模块定义) 指定一种机制,在该机制下模块和依赖可以异步加载。这对浏览器端的异步加载尤其适用。
- AMD 是 RequireJS 在推广过程中对模块定义的规范化产出。
- 使用AMD规范进行开发需要使用RequireJS
- requireJS主要解决两个问题:
- js文件之间的依赖关系:被依赖的文件需要早于依赖它的文件加载到浏览器
- js加载的时候浏览器会停止页面渲染,加载文件越多,页面失去响应时间越长
define(id?, dependencies?, factory);
id: 定义中模块的名字,可选;如果没有提供该参数,模块的名字应该默认为模块加载器请求的指定脚本的名字。
依赖dependencies:是一个当前模块依赖的,已被模块定义的模块标识的数组字面量。 依赖参数是可选的,如果忽略此参数,它应该默认为["require", "exports", "module"]。然而,如果工厂方法的长度属性小于3,加载器会选择以函数的长度属性指定的参数个数调用工厂方法。
工厂方法factory,模块初始化要执行的函数或对象。如果为函数,它应该只被执行一次。如果是对象,此对象应该为模块的输出值。
AMD模式
define和require这两个定义模块、调用模块的方法,合称为AMD模式。它的模块定义的方法非常清晰,不会污染全局环境,能够清楚地显示依赖关系。
AMD模式可以用于浏览器环境,并且允许非同步加载模块,也可以根据需要动态加载模块。
define方法:定义模块
define方法用于定义模块,RequireJS要求每个模块放在一个单独的文件里。
按照是否依赖其他模块,可以分成两种情况讨论。第一种情况是定义独立模块,即所定义的模块不依赖其他模块;第二种情况是定义非独立模块,即所定义的模块依赖于其他模块。
require方法:调用模块
require方法用于调用模块。它的参数与define方法类似。
require(['foo', 'bar'], function ( foo, bar ) {
foo.doSomething();
});
上面方法表示加载foo和bar两个模块,当这两个模块都加载成功后,执行一个回调函数。该回调函数就用来完成具体的任务。
require方法的第一个参数,是一个表示依赖关系的数组。这个数组可以写得很灵活,请看下面的例子。
require( [ window.JSON ? undefined : 'util/json2' ], function ( JSON ) {
JSON = JSON || window.JSON;
console.log( JSON.parse( '{ "JSON" : "HERE" }' ) );
});
上面代码加载JSON模块时,首先判断浏览器是否原生支持JSON对象。如果是的,则将undefined传入回调函数,否则加载util目录下的json2模块。
require方法也可以用在define方法内部。
define(function (require) {
var otherModule = require('otherModule');
});
CommonJS规范
CommonJS是服务器端模块的规范,Node.js采用了这个规范。Node.JS首先采用了js模块化的概念。
1.在一个模块中,存在一个自由的变量“require”,它是一个函数。
- 这个“require”函数接收一个模块标识符
- “require”返回外部模块所输出的API
- 如果出现依赖闭环(dependency cycle),那么外部模块在被它的传递依赖(transitive dependencies)所require的时候可能并没有执行完成;在这种情况下,”require”返回的对象必须至少包含此外部模块在调用require函数(会进入当前模块执行环境)之前就已经准备完毕的输出。
- 如果请求的模块不能返回,那么”require”必须抛出一个错误。
2.在一个模块中,会存在一个名为“exports”的自由变量,它是一个对象,模块可以在执行的时候把自身的API加入到其中。
3.模块必须使用“exports”对象来做为输出的唯一表示。
根据这个规范,每个文件就是一个模块,有自己的作用域。在一个文件里面定义的变量、函数、类,都是私有的,对其他文件不可见。
// example.js
var x = 5;
var addX = function (value) {
return value + x;
};
上面代码中,变量X和函数addX,是当前文件example.js私有的,其他文件不可见。如果想在多个文件分享变量,必须定义为global对象的属性。
global.warning = true;
上面代码的warning变量,可以被所有文件读取。当然,这样写法是不推荐的。
CommonJS规范规定,每个模块内部,module变量代表当前模块。这个变量是一个对象,它的exports属性(即module.exports)是对外的接口。加载某个模块,其实是加载该模块的module.exports属性。
var x = 5;
var addX = function (value) {
return value + x;
};
module.exports.x = x;
module.exports.addX = addX;
上面代码通过module.exports输出变量x和函数addX。
require方法用于加载模块。
var example = require('./example.js');
console.log(example.x); // 5
console.log(example.addX(1)); // 6
CommonJS模块的特点:
- 所有代码都运行在模块作用域,不会污染全局作用域。
- 模块可以多次加载,但是只会在第一次加载时运行一次,然后运行结果就被缓存了,以后再加载,就直接读取缓存结果。要想让模块再次运行,必须清除缓存。
- 模块加载的顺序,按照其在代码中出现的顺序。
RequireJS模块化加载实例
【个人总结,如有错漏,欢迎指出】
:>