26 January 2014

原文:Authoring AMD modules

AMD(Asynchronous Module Definition,异步模块定义)是最流行的 JavaScript 模块规范,被 cujo.jsjQuerydojoMootools,以及无数其他的库和框架所采用。AMD 专为浏览器环境而设计,也可以应用在非浏览器环境中。

编写 AMD 模块非常简单,只需要记住三点:

  1. 把代码封装到 define() 中。
  2. 罗列出依赖关系。
  3. 返回一个值。

define()

在 AMD 环境中,函数 define() 用来声明一个模块。它的参数签名非常灵活,不过我们先专注于最常见的用法:

define(dependencyIds, factoryFunction)

第一个参数 dependencyIds 是一个模块名(标识符)数组,指定了当前模块运行时所依赖的其他模块。第二个参数 factoryFunction 是一个工厂函数,用于创建当前模块,并且只会运行一次。工厂函数被调用时,它所依赖的模块被作为参数传入。并且,只有在所有依赖都就绪之后,工厂函数才会运行。事实上,通常工厂函数是在需要时才会运行。

下面是一个简单的例子。

// module app/mime-client
define(['rest', 'rest/interceptor/mime'], function (rest, mime) {
    var client;

    client = rest.chain(mime);

    return client;
});

模块“app/mime-client”依赖于另外两个模块:“rest” 和 “rest/interceptor/mime”。这两个模块被映射到了工厂函数的参数 restmime。当然,你也可以随意地重新命名它们。

请注意,模块名(标识符)中的斜杠并不意味着它是一个 URL,而是用来表示命名空间。在前面的例子中,模块“app/mime-client”依赖于命名空间“rest/interceptor”下的一个模块。(这里只是预热一下 AMD 的“包”概念,更多细节将在另一篇教程中介绍。)

在工厂函数中,我们创建了“app/mime-client”模块,并把它返回。此时该模块是一个函数,因为 rest.js 是一组 REST 功能的集合。不止如此,还可以创建任何有效 JavaScript 类型(即返回值)的模块。

兼容 CommonJS

AMD 的 define 还支持另外一种参数签名,以消除 AMD 和 CommonJS 之间的差异。工厂函数可以接受几个参数,其中,如果忽略了依赖模块数组 dependencyIds,AMD 会假设你想要模拟一个 CommonJS 模块。此时,CommonJS 规范中的 requireexportsmodule 会作为参数传给工作函数。这一变种通常被称为“AMD-wrapped CommonJS”,很神奇吧 ;)。

define(factoryFunction(require, exports, module))

下面是前一个例子的 AMD-wrapped CommonJS 版本。

// module app/mime-client
define(function (require, exports, module) {
    var rest, mime, client;

    rest = require('rest');
    mime = require('rest/interceptor/mime');

    client = rest.chain(mime);

    module.exports = client;
});

请注意,工厂函数接受了三个参数 requireexportsmodule,以此来模拟 CommonJS 规范(变种)。

在 CommonJS 中,依赖关系通过为变量设置 require(id) 来指定,这一约定被称为 free requirescoped require(不常见),而在 AMD 中,则被称为 local require

在这种情况下,你应该导出模块,而不是返回它。有两种方式可以实现这点。最简单的方式即上面例子所演示的,直接把模块赋予 module.exports。注意:module.exports = 并不是严格的 CommonJS 格式。然而不管如何,它算是一种对 CommonJS 的有效扩展方式,而 CommonJS 又是被广泛支持的。

第二种方式是在 exports 对象上设置属性。尽管可以用很多用例来演示每种导出机制,但这是一个很大的话题,我们会在后面的教程中展开。

其他变种

函数 define() 的参数签名还有许多其他的变种,即使是 AMD-wrapped CommonJS 也有许多变种 -- 以至于在一篇简单的教程中无法覆盖到,在阅读 cujo.js 的其他教程时,你会看到更多的变种。我比较喜欢下面这种方式,它用来声明符合 wire.js 规范的模块。

define({
    message: "I haz been wired",
    helloWired: {
        create: {
            module: 'hello-wired',
            args: { $ref: 'dom!hello' }
        },
        init: {
            sayHello: { $ref: 'message' }
        }
    },
    plugins: [
        { module: 'wire/dom' }
    ]
});

正如你所看到的,参数 factoryFunction 并不总是一个函数!如果导出的值是一个直接量,就可以省略掉包裹模块代码的工厂函数,就像上面的例子那样。AMD 会检测最后一个参数是否为函数,如果不是,则自动把它包裹到一个工厂函数中。同时,因为当前模块没有明确的依赖关系,所以也省略了依赖模块数组 dependencyIds

很简单,不是吗?

关于 AMD 模块的更多细节,请访问 https://github.com/amdjs/amdjs-api/wiki/



blog comments powered by Disqus