注册 登录  
 加关注
查看详情
   显示下一条  |  关闭
温馨提示!由于新浪微博认证机制调整,您的新浪微博帐号绑定已过期,请重新绑定!立即重新绑定新浪微博》  |  关闭

百鬼夜行

身是菩提树,心如明镜台,时时勤拂拭,勿使惹尘埃。

 
 
 

日志

 
 

AMD终极揭秘(1)  

2014-04-03 10:18:07|  分类: 工具类 |  标签: |举报 |字号 订阅

  下载LOFTER 我的照片书  |

原文: 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()方法,用于定义一个模块及其依赖。通常我们这样来写一个模块:

[javascript] view plaincopy
  1. define(dependencyIds, function(dependency1, dependency2,...){  
  2.    // module code  
  3. });  

dependencyIds 参数是一个字符串数组,用于表示需要加载的依赖模块。这些依赖模块将会被加载和执行。一旦所有依赖都被执行完毕,它们的输出将作为参数提供给回调函数(define()方法的第二个参数)

为了展示AMD的基础用法,我们可以定义一个使用dojo/query(css选择器查询)和dojo/on(事件处理)的模块。

[javascript] view plaincopy
  1. define(["dojo/query""dojo/on"],  
  2.         function(query, on){  
  3.     return {  
  4.         flashHeaderOnClick: function(button){  
  5.             on(button, "click"function(){  
  6.                 query(".header").style("color""red");  
  7.             });  
  8.         }  
  9.     };  
  10. });  

一旦dojo/query和dojo/on被加载(当然也必须等到它们本事的依赖也被加载,以此类推), 回调函数将被调用,同时dojo/query的输出(一个负责CSS选择器查询的函数)作为参数query,dojo/on的输出(一个可以添加事件监听器的函数)作为参数on被传到这个回调函数中。回调函数(通常认为是模块的工厂方法)被保证只调用一次。

列在依赖集合中的每个模块标识是一个抽象的模块路径。说它是抽象的因为它被模块加载器转移成真正的URL。正如你所见,模块路径并不需要包含“.js”后缀,这个后缀在加载的时候会自动添加。当模块标识直接由模块名打头时,该名称是模块的绝对标识。相比之下,我们也可以通过由“./”或者"../"打头表示当前目录或者父目录来指定相对标识。这些相对标识会通过标准路径解析规则来解析成绝对标识。你可以定义一个模块路径规则来决定这些模块路径将如何转换成URL。缺省情况下,模块根目录定义为相对于模块加载器包的父目录的路径。例如,如果我们用下面的方法加载Dojo(注意在这里我们设置async属性为true来保证异步AMD加载)

[javascript] view plaincopy
  1. <script src="/path/to/dojo/dojo.js" data-dojo-config="async:true"></script>  

那么,假设根目录到模块的路径为“/path/to/”。如果我们指定依赖于“my/module”,这个依赖将被解析为“/path/to/my/module.js”.

初始模块加载

我们已经描述了如何创建一个简单的模块。然而,我们还需要一个入口来触发这些依赖链。我们可以通过使用require() API来做到这一点。这个函数签名基本跟define()一致,区别在于它用于加载依赖但而不需要定义一个模块(当一个模块被定义时,如果它不被别的模块请求它是不会执行的)我们可以像下面这样加载我们的应用程序:

  1. <script src="/path/to/dojo/dojo.js"><!--mce:1--></script>  
  2. <script type="text/javascript"><!--mce:2--></script>  

Dojo提供了加载初始模块的快捷方式。初始模块能够通过指定deps配置属性来加载。

  1. <script src="/path/to/dojo/dojo.js"><!--mce:3--></script>  

这是加载应用程序的一个非常棒的方式,因为JavaScript代码能够完全从HTML中消除,仅需留下一个脚本标记来引导整个剩余的程序。同时,这种方式让你能够轻松的创建强劲的build,它能够将你的应用程序代码和dojo.js组合成为单独的一个文件而不需要在build之后改变HTML脚本标签。RequireJS和其他模块加载器也有类似的加载顶层模块的选项。


上图展示了由require()调用引起的一连串的依赖加载。require()的调用开启加载第一个模块,接着根据需要加载各模块的依赖模块。那些不需要的模块(如上图中的模块d)则永远不会被加载或者执行。

require()函数还可用于配置模块路径查找以及其他选项,但这一般来说对各个模块加载器都有特定的实现。更多信息请参考各个加载器关于配置细节的文档。

插件和Dojo最优化

AMD还支持加载其它资源的插件。这一点对于加载非AMD依赖非常有价值,例如加载HTML片段和模板,CSS,国际化相关的特定资源等。插件机制让我们在依赖列表中引用这些非AMD资源。语法如下:

[javascript] view plaincopy
  1. "plugin!resource-name"  

dojo/text插件就是一个常用的插件,它允许你直接将一个文件加载为一段文本。使用这个插件时,我们将目标文件列为资源名。在小部件加载它们的HTML模板时经常使用这个插件。例如,我们用下面的方式可以通过Dojo创建我们自己的小部件:

[javascript] view plaincopy
  1. define(["dojo/_base/declare""dijit/_WidgetBase""dijit/_TemplatedMixin""dojo/text!./templates/foo.html"],  
  2.         function(declare, _WidgetBase, _TemplatedMixin, template){  
  3.     return declare([_WidgetBase, _TemplatedMixin], {  
  4.         templateString: template  
  5.     });  
  6. });  

这是一个多层面创建Dojo小部件的范例。首先,它展示了利用Dijit基类创建小部件的标准用法。你可能也注意到我们如何创建一个小部件类并如何返回它。我们没有使用任何命名空间或者类名来使用declare()(类构造方法)。因为AMD消除了对命名空间的需求,我们不再需要用declare()来创建全局的类名。这一点与AMD模块中写匿名模块的策略是一致的。同样的,一个匿名的模块是不需要在其模块内部硬编码任何相对于自身的路径或者名称的。我们可以轻松的对模块重命名或者将其移动到其他的路径而不需要改模块内的任何代码。通常我们推荐使用这种方法来定义匿名类,但如果你需要用声明式的标记来使用这个小部件的话,为了创建一个具有命名空间的全局变量,使其能够让Dojo 解析器在Dojo1.7中引用,你还是需要包含命名空间/类名来定义类。Dojo 1.8中对此作了改进,你可以使用模块标识来做到这一点。

还有一些Dojo包含的插件是非常有用的。dojo/i18n插件在加载国际化区域性包(常用于翻译文本或者区域信息格式化)时使用。另外一个重要的插件是dojo/domReady,它通常被推荐用于取代dojo.ready。如果一个模块除了加载其他依赖还需要等待整个DOM可用才执行的时候,这个插件使得这一过程非常简单,不需要再加一层额外的回调。我们将dojo/domReady作为插件使用时,但需要对应的资源名。

[javascript] view plaincopy
  1. define(["dojo/query""dojo/domReady!"],  
  2.         function(query){  
  3.    // DOM is ready, so we can query away  
  4.    query(".some-class").forEach(function(node){  
  5.        // do something with these nodes  
  6.    });  
  7. });  

另一个有价值的插件是dojo/has。 这个模块用于辅助检测某些特征,帮助你基于当前浏览器的某些特征来选择不同的代码路径。然而这个模块也常被用作一个标准模块 ,提供一个has()函数,它也同时可以当作插件使用。作插件使用时能够帮助我们根据当前的特性条件性的加载某些依赖。dojo/has插件的语法采用了一个三元操作符,它将特性名称作为条件,而模块标识作为值。例如,我们可以在当然浏览器支持touch事件的条件下加载单独的touch UI模块:

[javascript] view plaincopy
  1. define(["dojo/has!touch?ui/touch:ui/desktop"],  
  2.   function(ui){  
  3.     // ui will be ui/touch if touch is enabled,  
  4.     //and ui/desktop otherwise  
  5.     ui.start();  
  6. });  
这个三元操作符可以是嵌套的,空字符串可用于表示不加载任何模块。

使用dojo/has的好处不仅仅是提供一个特征检测的运行时API。如果使用dojo/has,不光在你的代码中有has()形式,同时也作为依赖插件,build系统可以检测这些特性的分支。这意味着我们可以创建设备或者浏览器相关的build,它们能够为某些特定的特征集合进行高度优化,只需要在build里的staticHasFeatures选项定义期望的特性,然后build就会自动的处理相应的代码分支。

数据模块

对于没有任何依赖的模块,他们被简单的定义为一个对象(就像数据一样)。你可以使用仅有一个参数的define()调用,该参数就是那个对象。这是非常简单和直接明了的。
[javascript] view plaincopy
  1. define({  
  2.     foo: "bar"  
  3. });  

这与JSONP非常类似,支持基于脚本的JSON数据传输。但是,实际上AMD对于JSONP的优势在于它不需要请求任何URL参数,其目标可以是一个静态文件,不需要服务器端任何支持代码来为数据加上参数化的回调函数前缀。然而,这项技术必须小心使用。模块加载器总是会对模块进行缓存,因此后续的针对相同模块标识的require()请求会产生同样的缓存数据。这对你的检索需求可能会造成一定的困扰。

  评论这张
 
阅读(158)| 评论(0)
推荐 转载

历史上的今天

在LOFTER的更多文章

评论

<#--最新日志,群博日志--> <#--推荐日志--> <#--引用记录--> <#--博主推荐--> <#--随机阅读--> <#--首页推荐--> <#--历史上的今天--> <#--被推荐日志--> <#--上一篇,下一篇--> <#-- 热度 --> <#-- 网易新闻广告 --> <#--右边模块结构--> <#--评论模块结构--> <#--引用模块结构--> <#--博主发起的投票-->
 
 
 
 
 
 
 
 
 
 
 
 
 
 

页脚

网易公司版权所有 ©1997-2018