天天看点

Javascript 模块规范 CommonJS ,AMD ,CMD

1.CommonJS 模块规范

早就听说有用于本地编程的JavaScript框架,但总觉得是小打小闹不成气候,也没有什么实用价值。但自从CommonJS和NodeJS两个项目的出现,JavaScript作为本地编程语言的这种特殊应用形式,才开始进入进入大众的视野。 本文翻译CommonJS首页的介绍,说明什么是CommonJS。 另外随着并行计算的普及,像JavaScript这种函数式语言,由于其固有的易于进行并行计算的特性,将有更广阔的应用前景。

JavaScript是一个强大面向对象语言,它有很多快速高效的解释器。官方JavaScript标准定义的API是为了构建基于浏览器的应用程序。然而,并没有定于一个用于更广泛的应用程序的标准库

CommonJS API定义很多普通应用程序(主要指非浏览器的应用)使用的API,从而填补了这个空白。它的终极目标是提供一个类似Python,Ruby和Java标准库。这样的话,开发者可以使用CommonJS API编写应用程序,然后这些应用可以运行在不同的JavaScript解释器和不同的主机环境中。在兼容CommonJS的系统中,你可以实用JavaScript程序开发:

服务器端JavaScript应用程序

命令行工具

图形界面应用程序

混合应用程序(如,Titanium或Adobe AIR)

       NodeJS和CommonJS之间的关系

CommonJS是一种规范,NodeJS是这种规范的实现。CommonJS是一 个不断发展的规范,计划将要包括如下部分:

·        Modules

·        Binary strings and buffers

·        Charset encodings

·        Binary, buffered, and textual input and output (io) streams

·        System process arguments, environment, and streams

·        File system interface

·        Socket streams

·        Unit test assertions, running, and reporting

·        Web server gateway interface, JSGI

·        Local and remote packages and package management

具体每个子规范的定制进度请查看官方网站的说明:http://commonjs.org/specs/

CommonJS有很多实现,其中不乏很多大名鼎鼎的项目,比如说:Apache的CouchDB和node.js等。但这些项目大 部分只实现了CommonJS的部分规范。具体的项目和实现部分参见官方网站的说明:http://commonjs.org/impl/

千万别小看作为后台应用的JavaScript,Palm的WebOS的开发计划中就曾提到CommonJS和其扩展的JS API,另外几乎所有的平台(包括智能手机平台和计算机平台都开始流程)都开始引入Web技术:比如说S60中的WRT,IPhone中的 WebWidget,Android平台上的WebWidget,还有Firefox直接用XML和CSS作界面用Javascript控制逻辑,甚至 Google的ChromeOS直接就是一个浏览器操作系统。  我做个技术上的赌注:动态语言和静态语言混合编程将会很流行(比如Android和PalmWebOS同时引入两种SDK,QT直至 QTScript)。

示例:

console.log("evaluating example.js");
           var invisible = function(){
                 console.log("invisible");
           }
           exports.message="hi";
           exprots.say=function(){
           console.log(message);
      }
           

使用require方法,加载example.js

var example = require("./example.js");
           

这是,变量example就对应模块中的exports对象,于是就可以通过这个变量,使用模块提供的各个方法。

{
           message:"hi",
           say:[function]
      }
           

require方法默认读取js文件,说以可以省略js后缀名。

var example = require("./example");
           

js文件名前面需要加上路径,可以是相对路径(相对于使用require方法的文件),也可以是绝对路径。如果省略路径,node.js会认为,你要加载的是一个核心模块,或者已经安装在本地node_modules目录中的模块。如果加载的是一个目录,node.js会首先寻找该目录中的package.json文件,加载文件main属性提到模块,否则就寻找该目录下的index.js文件。

下面的例子是使用一行语句,定义一个最简单的模块。

文件名: addition.js

内容:

exports.do=function(a,b){return a + b;};
           

上面的语句定义了一个加法模块,做法就是在 exports对象上定义一个do方法,那就是提供外部调用的方法。使用时候,只要用require函数调用即可。

调用事例:

var add = require(“./addition”);
        var sum=add.do(1,2);
           

再看一个复杂一点的例子

//文件名:foobar.js
        //内容:
		function fooobar(){
			this.foo=function(){
				console.log("Hello foo");
			}
		
			this.bar = function(){
				console.log("Hello bar");
			}
		}
		exports.foobar=foobar;
           

调用该模块的方法如下:

var foobar = require("./foobar").foobar, test = new foobar();
           test.bar(); //Hello bar
           

有时,不需要exports返回一个对象,只需要它返回一个函数。这时,就要写成module.exports.

2.AMD规范与CommonJS规范的兼容性

CommonJS规范加载模块是同步的,也就是说,只有加载完成,才能执行后面的操作。AMD规范则是非同步加载模块,允许指定回调函数。由于Node.JS主要用于服务器编程,模块文件一般都已经存在于本地硬盘,所以加载起来比较快,不用考虑非同步加载的方式,所以CommonJS规范比较适用。但是,如果是游览器环境,要从服务器端加载模块,这时就必须采用非同步模式,因此游览器端一般采用AMD规范。

       AMD规范使用define方法定义模块,下面就是一个例子:

define(['package/lib'],function(lib){
     function foo(){
          lib.log('hello world !');
     }
     return{
          foo:foo
     };
});
           

AMD规范允许输出的模块兼容CommonJS规范,这时define方法需要写成下面的这样:

define(function(require,exports,module){
             var someModule =require('someModule');
             var anotherModule = require('anotherModule');
	
             someModule.doTehAwesome();
             anotherModule.doMoarAwesome();
             exports.asplode=function(){
                     someModule.doTehAwesome();
                     anotherModule.doMoarAwesome();
             }
        });
           

3.AMD

异步模块定义规范(AMD)制定了定义模块的规则,这样模块和模块的依赖可以被异步加载。这和浏览器的异步加载模块的环境刚好适应(浏览器同步加载模块会导致性能、可用性、调试和跨域访问等问题)。

define()函数

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

define(id?, dependencies?, factory);
           

ID  名字

第一个参数,id,是个字符串。它指的是定义中模块的名字,这个参数是可选的。如果没有提供该参数,模块的名字应该默认为模块加载器请求的指定脚本的名字。如果提供了该参数,模块名必须是“顶级”的和绝对的(不允许相对名字)。

模块名的格式

模块名用来唯一标识定义中模块,它们同样在依赖数组中使用。AMD的模块名规范是 CommonJS 模块名规范的超集。引用如下:

  • 模块名是由一个或多个单词以正斜杠为分隔符拼接成的字符串
  • 单词须为驼峰形式,或者".",".."
  • 模块名不允许文件扩展名的形式,如".js"
  • 模块名可以为 "相对的" 或 "顶级的"。如果首字符为"."或".."则为"相对的"模块名
  • 顶级的模块名从根命名空间的概念模块解析
  • 相对的模块名从 "require" 书写和调用的模块解析

上文引用的CommonJS模块id属性常被用于JavaScript模块。

相对模块名解析示例:

  • 如果模块 "a/b/c" 请求 "../d", 则解析为"a/d"
  • 如果模块 "a/b/c" 请求 "./e", 则解析为"a/b/e"

如果AMD的实现支持加载器插件(Loader-Plugins),则"!"符号用于分隔加载器插件模块名和插件资源名。由于插件资源名可以非常自由地命名,大多数字符都允许在插件资源名使用。

dependencies依赖

第二个参数,dependencies,是个定义中模块所依赖模块的数组。依赖模块必须根据模块的工厂方法优先级执行,并且执行的结果应该按照依赖数组中的位置顺序以参数的形式传入(定义中模块的)工厂方法中。

依赖的模块名如果是相对的,应该解析为相对定义中的模块。换句话来说,相对名解析为相对于模块的名字,并非相对于寻找该模块的名字的路径。

本规范定义了三种特殊的依赖关键字。如果"require","exports",或"module"出现在依赖列表中,参数应该按照CommonJS模块规范自由变量去解析。

依赖参数是可选的,如果忽略此参数,它应该默认为["require", "exports", "module"]。然而,如果工厂方法的形参个数小于3,加载器会选择以函数指定的参数个数调用工厂方法。

factory 工厂方法

第三个参数,factory,为模块初始化要执行的函数或对象。如果为函数,它应该只被执行一次。如果是对象,此对象应该为模块的输出值。

如果工厂方法返回一个值(对象,函数,或任意强制类型转换为true的值),应该为设置为模块的输出值。

简单的CommonJS转换

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

在某些情况下,因为脚本大小的限制或函数不支持toString方法(Opera Mobile是已知的不支持函数的toString方法),模块加载器可以选择扫描不扫描依赖性。

如果有依赖参数,模块加载器不应该在工厂方法中扫描依赖性。

define.amd 属性

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

当前,define.amd对象的属性没有包含在本规范中。实现本规范的作者,可以用它通知超出本规范编程接口基本实现的额外能力。

define.amd的存在表明函数遵循本规范。如果有另外一个版本的编程接口,那么应该定义另外一个属性,如define.amd2,表明实现只遵循该版本的编程接口。

一个如何定义同一个环境中允许多次加载同一个版本的模块的实现:

define.amd = {
      multiversion: true
};
           

最简短的定义:

define.amd = {};
           

一次输出多个模块

在一个脚本中可以使用多次define调用。这些define调用的顺序不应该是重要的。早一些的模块定义中所指定的依赖,可以在同一脚本中晚一些定义。模块加载器负责延迟加载未解决的依赖,直到全部脚本加载完毕,防止没必要的请求。

例子

使用require和exports

创建一个名为"alpha"的模块,使用了require,exports,和名为"beta"的模块:

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

一个返回对象的匿名模块:

define(["alpha"], function (alpha) {
       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 () {};
});
           

全局变量

本规范保留全局变量"define"以用来实现本规范。包额外信息异步定义编程接口是为将来的CommonJS API保留的。模块加载器不应在此函数添加额外的方法或属性。

本规范保留全局变量"require"被模块加载器使用。模块加载器可以在合适的情况下自由地使用该全局变量。它可以使用这个变量或添加任何属性以完成模块加载器的特定功能。它同样也可以选择完全不使用"require"。

使用注意

为了使静态分析工具(如build工具)可以正常工作,推荐使用字面上形如的'define(...)'。

与CommonJS的关系

一个关于本API的wiki开始在CommonJSwiki中创建了,作为中转的格式,模块中转。但是为了包含模块定义接口,随着时间而不断改变。在CommonJS列表中关于推荐本API作为模块定义API尚未达成一致。本API被转移到它自己的wiki和讨论组中。

AMD可以作为CommonJS模块一个中转的版本只要CommonJS没有被用来同步的require调用。使用同步require调用的CommonJS代码可以被转换为使用回调风格的AMD模块加载器。

Loader Plugins插件加载器

插件加载器是AMD规范的延生,它允许使用非传统的方法加载 JavaScript依赖

本API规范将允许使用插件加载器作为一种优化手段,将那些对该插件加载器敏感的插件资源加载到文本中。出于兼容性考虑,一个插件加载器应该设计成能够在众多JavaScript环境下使用. 例如 browser, Node 或者 Rhino.

术语

  插件依赖(plugindependency)是指按照AMD规范的插件加载器来加载的依赖. 它的格式如下:

[Plugin Module ID]![resource ID]
           

插件模块ID(plugin module ID)   是一个普通的AMD模块ID名,它是实现了插件加载器API的JavaScript模块, 资源 ID(resource ID) 是一个特定的插件标示字符串,它的作用是让插件加载器知晓如何去解析这个资源.

插件加载器例子

下面是一个用 text 插件加载器去加载一份HTML模板.

define(['text!../templates/start.html'], function (template) {
       	//do something with the template text string.
}
           

下面是另外一个使用更加复杂的资源ID结构。这是一个人为的例子.它只选择第一个匹配到索引数组的模块ID名。因此,下面的impl变量的结果是'./b'所代表的模块:

define(function (require) {
        var impl = require('index!1:./a:./b:./c');
});
           

API使用说明

load: function (resourceId, require, load, config)
           

load是用来加载资源的函数。为了能够加载插件,这是一个必需要实现的API方法。我们假设资源ID不需要特别的normalization(参见normalize() method),下面是该方法的详细描述。

  • resourceId: 字符串类型. 待加载的插件资源ID名。该ID名必须是标准化(normalized)后的。
  • require: 函数类型. 一个用来加载其他模块的本地require函数。该require函数拥有如下属性:
    • require.toUrl("moduleId+extension"). 更多信息,请参见require.toUrl API notes。
  • load: 函数类型.当资源可用时,该函数将会且仅会被调用一次。它将插件加载完成的信息反馈给插件加载器。
  • config: 对象类型, 可选。一个配置对象。 它给优化工具和web app提供了一种传递配置信息的方法。 如果编译插件作为优化工具的一部分时,优化工具可以通过设置编译的属性配置为真来编译插件。

下面是一个普通的js模块加载,没做其他任何事情:

define({
        load: function (name, req, load, config) {
            //req has the same API as require().
            req([name], function (value) {
                load(value);
            });
        }
});
           

normalize: function(resourceId, normalize)

  一个将传入的资源ID标准化的函数。模块ID的标准化通常指转换相对路径,比如将'./some/path' 或者 '../another/path'转化成不含相对路径的绝对模块ID名。这对于使用缓存和优化来说将非常有用,但是只在如下情况下需要实现

  • 资源ID的标准化过于复杂。
  • 只在资源名不是模块名时需要。

  如果插件没有哦实现normalize,那么加载器将默认它是规则的模块ID,并且试图标准化该模块ID。

需要标准化的参数:

  • resourceId: string类型. 待标准化的资源ID.
  • normalize: Function类型.一个依照当前加载器的配置,使用标准的模块相对路径转化规制,将传入的字符串ID转化成标准化模块ID的函数。

例如:假设有个index!加载器,它将根据给出的模块名序列加载模块。这是一个反例,仅仅又来验证假设。This is a contrived example, just to illustrate the concept. 一个模块可能依赖于加载器提供的依赖,如下所示:

define(['index!2?./a:./b:./c'], function (indexResource) {
        //indexResource will be the module that corresponds to './c'.
});
           

在这个例子中,已经标准化的IDs'./a', './b',以及 './c'将决定是否加载这个资源. 由于加载器不知道怎样去分析'index!2?./a:./b:./c',并将其标准化为 './a', './b', 以及 './c', 它需要插件来提供信息。这就是调用标准化函数的目的。

通过标准化的资源名称,这将使得加载器缓存值得到有效利用,并在优化器中正确地构建一个优化构建层。与此同时,加载器也可以将标准化ID传递到插件load方法中。

index!插件也可以这么使用:

(function () {
        //Helper function to parse the 'N?value:value:value'
        //format used in the resource name.
        function parse(name) {
            var parts = name.split('?'),
                index = parseInt(parts[0], 10),
                choices = parts[1].split(':'),
                choice = choices[index];

            return {
                index: index,
                choices: choices,
                choice: choice
            };
        }

        //Main module definition.
        define({
            normalize: function (name, normalize) {
                var parsed = parse(name),
                    choices = parsed.choices;

                //Normalize each path choice.
                for (i = 0; i < choices.length; i++) {
                    //Call the normalize() method passed in
                    //to this function to normalize each
                    //module ID.
                    choices[i] = normalize(choices[i]);
                }

                return parsed.index + '?' + choices.join(':');
            },

            load: function (name, require, load, config) {
                require([parse(name).choice], function (value) {
                    load(value);
                });
            }
        });

}());
           

require

  require是一个基于AMD规范实现的函数,它区别于传统的CommonJS require规范。因为它能够异步地加载动态的依赖,所以,我们对基于require的回调方式有了更多的需求。

API规范

  局部require vs 全局require

   局部require可以被解析成一个合符AMD工厂函数规范的require函数。

例如:

define(['require'], function (require) {
        //the require in here is a local require.
    });

    define(function (require, exports, module) {
        //the require in here is a local require.
    });
           

局部require也支持其他标准实现的API。

全局require函数作用于全局,和define()类似。 全局require和局部require有着相同的行为,包含以下特征:

1.模块ID应该认为是一个绝对的模块名称,而不是相对另一个模块的ID。

2.只有在异步的时候,才可以使用require(id, callback?)的回调形式。因为异步加载模块的方式是先发出一个异步请求,然后等主线程代码段执行完毕才能进行异步回调来处理加载好的模块。

实际中,我们经常会遇到一些阻塞模块加载的依赖,如果交互次数很多,需要大量的模块加载,应该采用全局依赖的形式去加载顶层模块。

require(String)

基于以下规范CommonJS Modules 1.1.1 require.根据参数,同步地返回模块ID所代表的模块。

如果模块没有加载或者执行完成,就会抛出错误。特别需要指出的是,在同步加载的回调中,如果模块没有加载完成,禁止动态的获取模块,否则,就会抛出异常。

使用define()定义模块时,依赖项中可以找到一个AMD模块:

define(function (require) {
        var a = require('a');
});
           

工厂方法可以被解析成require('')的调用形式(例如,使用语法解析器或者使用Function.prototype.toString()或者正则表达式)去找到依赖,加载并且执行依赖,然后执行工厂方法内部代码,通过这样的方式,就可以获取到模块。

require(Array, Function)

参数Array是一个由模块ID组成的数组。当模块ID所以代表的模块加载完成且可用时,回调函数Function才开始执行,并且只被执行一次。各个模块按照依赖数组中的位置顺序以参数的形式传入到Function里。

例如:

define(function (require) {
        require(['a', 'b'], function (a, b) {
            //modules a and b are now available for use.
        });
    });
           

require.toUrl(String)

将形如[module ID] +'.extension'这种字符形式转化成URL路径。

require.toUrl()方法采用通用的模块ID路径转化规则,将模块ID字符解析成URL路径.但它不支持以".js"这种扩展形式。所以,我们必须将'.extension'添加到了解析路径里。

例如:

//cart.js contents:
    define (function(require) {
        // 模块ID名 './templates/a'
        // 扩展名 '.html'
        // 模板路径大致以这样的形式结尾 'modules/cart/templates/a.html'
        var templatePath = require.toUrl('./templates/a.html');
    });
           

4.CMD 模块定义规范

在大名鼎鼎的玉伯 Sea.js 中,所有 JavaScript 模块都遵循 CMD(Common Module Definition) 模块定义规范。该规范明确了模块的基本书写格式和基本交互规则。

在 CMD 规范中,一个模块就是一个文件。代码的书写格式如下:

define(factory);
           

define Function

define 是一个全局函数,用来定义模块。

define define(factory)

define 接受 factory 参数,factory可以是一个函数,也可以是一个对象或字符串。

factory 为对象、字符串时,表示模块的接口就是该对象、字符串。比如可以如下定义一个 JSON 数据模块:

define({ "foo": "bar" });
           

也可以通过字符串定义模板模块:

define('I am a template. My name is {{name}}.');
           

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 require(id)

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

define(function(require, exports) {
       // 获取模块 a 的接口
       var a = require('./a');
       // 调用模块 a 的方法
       a.doSomething();
});
           

注意:在开发时,require 的书写需要遵循一些 简单约定。

require 书写约定

1. 正确拼写

模块 factory 构造方法的第一个参数必须 命名为 require 。

// 错误!
define(function(req) {
  				// ...
});

// 正确!
define(function(require) {
  				// ...
});
           

2. 不要修改

不要重命名 require 函数,或在任何作用域中给 require 重新赋值。

// 错误 - 重命名 "require"!
var req = require, mod = req("./mod");

// 错误 - 重定义 "require"!
require = function() {};

// 错误 - 重定义 "require" 为函数参数!
function F(require) {}

// 错误 - 在内嵌作用域内重定义了 "require"!
function F() {
  				var require = function() {};
}
           

3. 使用直接量

require 的参数值 必须 是字符串直接量。

// 错误!
require(myModule);
// 错误!
require("my-" + "module");
// 错误!
require("MY-MODULE".toLowerCase());
// 正确!
require("my-module");
           

在书写模块代码时,必须遵循这些规则。其实只要把require 看做是语法关键字 就好啦。

关于动态依赖

有时会希望可以使用 require 来进行条件加载:

if (todayIsWeekend)
   require("play");
else
   require("work");
           

但请牢记,从静态分析的角度来看,这个模块同时依赖 play 和 work 两个模块,加载器会把这两个模块文件都下载下来。 这种情况下,推荐使用 require.async 来进行条件加载。

require.asyncrequire.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.resolverequire.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.dependenciesArray

dependencies 是一个数组,表示当前模块的依赖。

module.exportsObject

当前模块对外提供的接口。

传给 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
});
           

小结

这就是 CMD 模块定义规范的所有内容。经常使用的 API 只有 define, require, require.async, exports, module.exports 这五个。其他 API 有个印象就好,在需要时再来查文档,不用刻意去记。

与 RequireJS 的 AMD 规范相比,CMD 规范尽量保持简单,并与 CommonJS 和 Node.js 的 Modules 规范保持了很大的兼容性。

AMD和 CMD的区别有哪些?

AMD 是 RequireJS 在推广过程中对模块定义的规范化产出。

CMD 是 SeaJS 在推广过程中对模块定义的规范化产出。

区别:

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

2.CMD 推崇依赖就近,AMD推崇依赖前置。看代码:

// 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 的作者默认是最喜欢上面的写法,也是官方文档里默认的模块定义写法。

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

标注:资料来源:维基,玉伯博客

继续阅读