AMD终极揭秘
原文: http://www.sitepen.com/blog/2012/06/25/amd-the-definitive-source/
作者:Kris Zyp
译者:Elaine Liu
究竟什么是AMD?随着web应用不断发展和对JavaScript依赖的进一步加深,出现了使用模块(Modules)来组织代码和依赖性。模块使得我们创建明确清晰的组件和接口,这些组件和接口能够很容易的加载并连接到其依赖组件。 AMD模块系统提供了使用JavaScript模块来构建Web应用的完美方式,并且这种方式具有形式简单,异步加载和广泛采用的特点。
异步模块定义(AMD)格式是一套API,它用于定义可重用的并能在多种框架使用的模块。开发AMD是为了提供一种定义模块的方式,这种方式可以使用原生的浏览器脚本元素机制来实现模块的异步加载。AMD API由2009年Dojo 社区的讨论中产生,然后移动到讨论CommonJS如何更好的为浏览器适应CommonJS模块格式(被NodeJS使用)。 CommonJS已经发展成为单独的一个标准并有其专门的社区。AMD已经广泛普及,形成了众多模块加载实现并被广泛使用。在SitePen公司,我们广泛的使用Dojo的AMD机制工作,为其提供支持,并积极的建设这一机制。
本文中用到的一些重要词汇模块(module) —— 一个经过封装的JavaScript文件,它遵循模块的格式,指定依赖和提供模块输出。模块标识(module ID)——唯一标识模块的字符串,相对模块标识将根据当前模块的标识解释为绝对模块标识模块路径 (module path)——用于检索模块的URL。一个模块标识对应于一个模块路径,该路径是由加载器配置规则设定的(缺省情况下,模块路径假定为该模块对于根路径的相对路径,根路径通常是模块加载器包所在的父目录)。模块加载器(module loader)——解析和加载模块以及相关依赖的JavaScript代码,它与插件交互,并处理加载配置。包(package)——一组模块集合。例如dojo,dijit以及dgrid都是包。构建器(builder)——用于将模块(或者多个模块)以及其依赖连接在一起产生单个JavaScript文件的工具,这样使得一个应用程序能够包含多个模块,并能创建多个构建层次,从而使得它们在被加载时实现HTTP请求数目最小化。层(layer)——一个文件,它包含若干模块并由构建器优化生成单个文件。依赖(dependency)——为了使另一个模块正常工作而必须加载的模块。AMD——异步模块定义,一种为浏览器开发提供最优体验的模块定义格式。工厂方法(factory)——通过define定义的并提供给模块加载器的函数,它在所有依赖加载完后执行一次。为什么需要AMD模块?模块化系统的基础前提是:
允许创建被封装的代码片段,也就是所谓的模块定义本模块与其他模块之间的依赖定义可以被其他模块使用的输出的功能谨慎的使用这些模块提供的功能AMD满足以上需求,并将依赖模块设置为其回调函数的参数从而实现在模块代码被执行前异步的加载这些依赖模块。AMD还提供了加载非AMD资源的插件系统。虽然有其他的load JavaScript的替代方法,但使用脚本元素来加载JavaScript有特有的优势,包括性能,减少调试(尤其在一些老版本的浏览器上)以及跨域的支持。因此AMD致力于提供基于浏览器开发的最优体验。AMD格式提供了几个关键的好处。首先,它提供了一种紧凑的声明依赖的方式。通过简单的字符串数组来定义模块依赖,使得开发者能够花很小的代价轻松列举大量模块依赖性。
AMD帮助消除对全局变量的需求。 每个模块都通过局部变量引用或者返回对象来定义其依赖模块以及输出功能。因此,模块不需要引入全局变量就能够定义其功能并实现与其他模块的交互。AMD同时是“匿名的”,意味着模块不需要硬编码指向其路径的引用, 模块名仅依赖其文件名和目录路径,极大的降低了重构的工作量。
通过将依赖性映射为局部变量, AMD鼓励高效能的编码实践。如果没有AMD模块加载器,传统的JavaScript代码必须依赖层层嵌套的对象来“命名”给定的脚本或者模块。如果使用这种方式,通常需要通过一组属性来访问某个功能,这会造成全局变量的查找和众多属性的查找,增加了额外的开发工作同时降低了程序的性能。通过将模块依赖性映射为局部变量,只需要一个简单的局部变量就能访问某个功能,这是极其快速的并且能够被JavaScript引擎优化。
使用AMD最基础的AMD API是define()方法,用于定义一个模块及其依赖。通常我们这样来写一个模块:
上图展示了由require()调用引起的一连串的依赖加载。require()的调用开启加载第一个模块,接着根据需要加载各模块的依赖模块。那些不需要的模块(如上图中的模块d)则永远不会被加载或者执行。
require()函数还可用于配置模块路径查找以及其他选项,但这一般来说对各个模块加载器都有特定的实现。更多信息请参考各个加载器关于配置细节的文档。
插件和Dojo最优化AMD还支持加载其它资源的插件。这一点对于加载非AMD依赖非常有价值,例如加载HTML片段和模板,CSS,国际化相关的特定资源等。插件机制让我们在依赖列表中引用这些非AMD资源。语法如下:
使用构建

(译者注:原文作者选用的实验截图可能是本地资源加载的情况,由于本地资源加载的随机性,在使用构建之后优势不明显。但实际在网络传输中,使用构建会大大减少加载时间。)
性能就像前面提到的那样,使用脚本元素注入比其他的方法快是因为它更依赖于原生的浏览器脚本加载机制。我们基于dojo.js创建了一些模块的测试用例,脚本元素加载比使用XHR eval的方式快了大概60-90%。在Chrome中,如果有大量的小模块,每个模块加载的时间大概是5-6ms
,而XHR+eval方式平均每个模块加载时间则接近9-10ms。在Firefox中,同步XHR方式比异步方式更快,而在IE中异步XHR比同步的快,但脚本元素加载无疑是最快的一个。让我们感到意外的是IE9是最快的一个浏览器,不过这有可能是因为在Firefox和Chrome中debugger/inspector增加了一些额外的性能开销。

AMD API是开放的,现在已有有多个AMD模块加载器和构造器的实现。这里介绍几个重要的AMD加载器:
Dojo – 这是一个完全的包括插件和构造器的AMD加载器。这是我们通常用来实现Dojo工具包的加载器。
import {query} from "dojo/query.js";import {on} from "dojo/on.js";export function flashHeaderOnClick(button){ on(button, "click", function(){ query(".header").style("color", "red"); });}现在提出的新模块系统包括支持定制的模块加载器,它能够与新的模块系统交互,还能够用于保留某些AMD现存的某些特性,例如用插件访问非JavaScript资源。