本文内容收集整理自网络

一、CMD规范

1、定义

CMD即Common Module Definition(通用模块定义), 是SeaJS 在推广过程中对模块定义的规范化产出的。在 CMD 规范中,一个模块就是一个文件。

2、API说明

  • define(factory)

define是一个全局函数,用来定义模块。define接受一个参数factory,该参数可以是一个函数,也可以是一个对象或字符串。

factory为对象、字符串时,表示模块的接口就是该对象、字符串,例如

define({ "foo": "bar" });
define('I am a template. My name is .');

factory为函数时,表示是模块的构造方法。执行该构造方法,可以得到模块向外提供的接口。factory方法在执行时,默认会传入三个参数:requireexportsmodule

define(function(require, exports, module) {
	// 模块代码
});

define也可以接受两个以上参数:

/*
 * define(id?, deps?, factory)
 *
 * id:模块标识(String)
 * deps:模块依赖(Array)
 *
 * id 和 deps 可以省略,省略时,可以通过构建工具自动生成。
 */
define('hello', ['jquery'], function(require, exports, module) {
	// 模块代码
});
注意:带 id 和 deps 参数的 define 用法不属于 CMD 规范,而属于 Modules/Transport 规范。

define.cmd:一个空对象,可用来判定当前页面是否有CMD模块加载器,例如:

if (typeof define === "function" && define.cmd) {
	// 有 Sea.js 等 CMD 模块加载器存在
}
  • require

require是一个方法(Function),接受模块标识作为唯一参数,用来获取其他模块提供的接口。例如:

define(function(require, exports) {
	// 获取模块 a 的接口
	var a = require('./a');
	// 调用模块 a 的方法
	a.doSomething();
});
注意:在开发时,require 的书写需要遵循一些简单约定
  • require.async(id, callback?)

用来在模块内部异步加载模块,并在加载完成后执行指定回调函数。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(id)

使用模块系统内部的路径解析机制来解析并返回模块路径。该函数不会加载模块,只返回解析后的绝对路径。例如:

define(function(require, exports) {
	console.log(require.resolve('./b'));//http://example.com/path/to/b.js
});

这可以用来获取模块路径,一般用在插件环境或需动态拼接模块路径的场景下。

  • require.cache

查看模块系统加载过的所有模块。

某些情况下,需要重新加载某个模块,可以得到该模块的url后通过delete require.cache[url] 来将其信息删除。

  • exports

exports是一个对象(Object),用来向外提供模块接口。例如:

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 模块。

  • module

module是一个对象(Object),存储了与当前模块相关联的一些属性和方法。

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)

  • 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
});
提示:exports 仅仅是 module.exports 的一个引用。在 factory 内部给 exports 重新赋值时,并不会改变 module.exports 的值。因此给 exports赋值是无效的,不能用来更改模块接口。例如:
define(function(require, exports) {
	// 错误用法!!!
	exports = {
		foo: 'bar',
		doSomething: function() {}
	};
});

define(function(require, exports, module) {
	// 正确写法
	module.exports = {
		foo: 'bar',
		doSomething: function() {}
	};
});
注意:对 module.exports 的赋值需要同步执行,不能放在回调函数里。
  • module.parent

初始化调用当前模块的模块。可以得到模块初始化的callstack。

  • module.factory

define(factory)中的factory参数

二、AMD规范

1、定义

AMD即Asynchronous Module Definition(异步模块定义),是 RequireJS 在推广过程中对模块定义的规范化产出的。异步模块规范 API 定义了一种模块机制,这种机制下,模块和它的依赖可以异步的加载。这个非常适合于浏览器环境,因为同步的加载模块会对性能、可用性、debug调试、跨域访问产生问题。

2、API说明

本规范只定义了一个函数 define,它是全局变量。函数的描述为:

define(id?, dependencies?, factory);
  • id:模块名称(String类型,可选)

    • 说明

      如果没有提供该参数,模块的名字应该默认为模块加载器请求的指定脚本的名字。如果提供了该参数,模块名必须是”顶级”的和绝对的(不允许相对名字)。

    • 命名规范

      AMD的模块名规范是CommonJS模块名规范的超集,引用如下:

      • 模块名是用正斜杠分割的有意义单词的字符串;
      • 单词须为驼峰形式,或者”.”,”..”;
      • 模块名不允许文件扩展名的形式,如”.js”;
      • 模块名可以为 “相对的” 或 “顶级的”。如果首字符为”.”或”..”则为相对的模块名。
      • 顶级的模块名从根命名空间的概念模块解析。
      • 相对的模块名从 require 书写和调用的模块解析。
  
  • dependencies:模块所依赖模块的数组(Array类型,可选)

依赖模块必须比factory优先执行,并且执行的结果应该按照依赖数组中的位置顺序。参数的依赖模块名如果是相对的,应该解析为相对定义中模块。

该参数是可选的,如果忽略此参数,它应该默认为[“require”, “exports”, “module”]。然而,如果factory的长度属性小于3,加载器会选择以函数的长度属性指定的参数个数调用factory

  • factory:模块初始化执行的函数或对象。

如果是对象,则为模块的输出值。
如果是函数,它只被执行一次。如果该方法返回一个值(对象,函数,或任意强制类型转换为true的值),则设置为模块的输出值。

  • define.amd

为了清晰的标识全局函数(为浏览器加载script必须的)遵从AMD编程接口,任何全局函数应该有一个amd的属性,它的值为一个对象。这样可以防止与现有定义了define的函数、但不遵从AMD编程接口的代码相冲突。

  • 简单的 CommonJS 转换:

如果依赖性参数被忽略,模块加载器可以选择扫描factory中的require语句以获得依赖性(字面量形为require(“module-id”))。第一个参数必须字面量为require从而使此机制正常工作。如果有依赖参数,模块加载器不应该在factory方法中扫描依赖性。

3、示例

define("alpha", ["require", "exports", "beta"], function (require, exports, beta) {
	exports.verb = function() {
		return beta.verb();
		//Or:
		return require("beta").verb();
	}
});

上面的代码创建一个名为”alpha”的模块,使用了requireexports,和名为beta的模块:

  • 一个返回对象的匿名模块:
define(['alpha'],function(){
	return {
	    verb : function(){
			return alpha.verb() + 2;
		}
	};
});
  • 一个没有依赖的模块可以直接定义对象:
define({
	add : function(x,y){
		return x + y;
	}
});
  • 一个使用了简单CommonJS转换的模块定义:
define(function(require,exports,module){
	var a = require('a'), 
		b = require('b');
	exports.action = function(){};
});

三、区别

对于依赖的模块,AMD 是提前执行,CMD 是延迟执行。不过 RequireJS 从 2.0 开始,也改成可以延迟执行(根据写法不同,处理方式不同)。CMD 推崇 as lazy as possible

CMD 推崇依赖就近:代码在运行时,首先是不知道依赖的,需要遍历所有的require关键字,找出后面的依赖。具体做法是将function toString后,用正则匹配出require关键字后面的依赖。 AMD 推崇依赖前置:无需遍历整个函数体就能知晓它的依赖,因此性能有所提升,缺点就是开发者必须显式得指明依赖 – 这会使得开发工作量变大,比如:当依赖项有n个时候 那么写起来比较烦且容易出错。不过 RequireJS 从 2.0 开始,也改成可以延迟执行(根据写法不同,处理方式不同)。

  • CMD
define(function(require, exports, module) {
	var a = require('./a')
	a.doSomething()
	... 
	var b = require('./b') // 依赖可以就近书写
	b.doSomething()
	... 
});
  • AMD
define(['./a', './b'], function(a, b) {
	// 依赖必须一开始就写好
	a.doSomething()
	... 
	b.doSomething()
	...
});

虽然 AMD 也支持 CMD 的写法,同时还支持将 require 作为依赖项传递,但 RequireJS 的作者默认是最喜欢上面的写法,也是官方文档里默认的模块定义写法。

从API设计角度来看,AMD 的 API 默认是一个当多个用,CMD 的 API 严格区分,推崇职责单一。比如 AMD 里,require 分全局 require 和局部 require,都叫 require。CMD 里,没有全局 require,而是根据模块系统的完备性,提供 seajs.use 来实现模块系统的加载启动。CMD 里,每个 API 都简单纯粹。

附:

Common Module Definition