dojo AMD规范

摘要:AMD 规范是JavaScript开发的一次重要尝试,它以简单而优雅的方式统一了JavaScript的模块定义和加载机制,并迅速得到很多框架的认可和采纳。这对开发人员来说是一个好消息。

  1. AMD的由来

  前端技术虽然在不断发展之中,却一直没有质的飞跃。除了已有的各大著名框架,比如Dojo,JQuery,ExtJs等等,很多公司也都有着自己的前端开发框架。这些框架的使用效率以及开发质量在很大程度上都取决于开发者对其的熟悉程度,以及对JavaScript的熟悉程度,这也是为什么很多公司的技术带头人都喜欢开发一个自己的框架。开发一个自己会用的框架并不难,但开发一个大家都喜欢的框架却很难。

  从一个框架迁移到一个新的框架,开发者很有可能还会按照原有框架的思维去思考和解决问题。这其中的一个重要原因就是JavaScript本身的灵活性:框架没办法绝对的约束你的行为,一件事情总可以用多种途径去实现,所以我们只能在方法学上去引导正确的实施方法。庆幸的是,在这个层面上的软件方法学研究,一直有人在去不断的尝试和改进,CommonJS就是其中的一个重要组织。他们提出了许多新的JavaScript架构方案和标准,希望能为前端开发提供银蛋,提供统一的指引。

  AMD规范就是其中比较著名一个,全称是Asynchronous Module Definition,即异步模块加载机制。从它的规范描述页面看,AMD很短也很简单,但它却完整描述了模块的定义,依赖关系,引用关系以及加载机制。从它被requireJS,NodeJs,Dojo,JQuery使用也可以看出它具有很大的价值,没错,JQuery近期也采用了AMD规范。在这篇文章中,我们就将介绍AMD的性质,用法,优势以及应用场景。从AMD中我们也能学习到如何在更高层面去设计自己的前端应用。

  2. AMD是什么

  作为一个规范,只需定义其语法API,而不关心其实现。AMD规范简单到只有一个API,即define函数:

    
    
define([module - name ? ],[array - of - dependencies ? ],[module - factory - or - object]);

  其中:

  • module-name: 模块标识,可以省略。
  • array-of-dependencies: 所依赖的模块,可以省略。
  • module-factory-or-object: 模块的实现,或者一个JavaScript对象。

  从中可以看到,第一个参数和第二个参数都是可以省略的,第三个参数则是模块的具体实现本身。后面将介绍在不同的应用场景下,他们会使用不同的参数组合。

  从这个define函数AMD中的A:Asynchronous,我们也不难想到define函数具有的另外一个性质,异步性。当define函数执行时,它首先会异步的去调用第二个参数中列出的依赖模块,当所有的模块被载入完成之后,如果第三个参数是一个回调函数则执行,然后告诉系统模块可用,也就通知了依赖于自己的模块自己已经可用。如果对应到dojo1.6之前的实现,那么在功能上可以有如下对应关系:

  • module-name: dojo.provide
  • dependencies: dojo.require
  • module-factory: dojo.declare

  不同的是,在加载依赖项时,AMD用的是异步,而dojo.require是同步。异步和同步的区别显而易见,前者不会阻塞浏览器,有更好的性能和灵活性。而对于NodeJs这样的服务器端AMD,则模块载入无需阻塞服务器进程,同样提高了性能。

  3. AMD实例:如何定义一个模块

  下面代码定义了一个alpha模块,并且依赖于内置的require,exports模块,以及外部的beta模块。可以看到,第三个参数是回调函数,可以直接使用依赖的模块,他们按依赖声明顺序作为参数提供给回调函数。(当依赖模块都加载完毕后可以执行你的模块

  这里的require函数让你能够随时去依赖一个模块,即取得模块的引用,从而即使模块没有作为参数定义,也能够被使用;exports是定义的alpha 模块的实体,在其上定义的任何属性和方法也就是alpha模块的属性和方法。通过exports.verb = ...就是为alpha模块定义了一个verb方法。例子中是简单调用了模块beta的verb方法。

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

模块的引用

define([ "dojo/cookie","dijit/Tree" ],function ( cookie,Tree){  
  var cookieValue = cookie( "cookieName" ) ; // get a cookie  
  new Tree( …) ; // create a dijit.Tree  
}) ; 

这样做相对于使用嵌套对象作为命名空间的来引用模块中的对象的传统方法来说有很大优势。因为我们不再需要重复的去键入一个完整的命名空间路径了. (译者: 过去要引用dijit.Tree 的中的某个方法,我们需要每次都使用dijit.tree)而现在我们只需要在依赖定义中给出一次完整的路径 (dojo/cookie 和dijit/tree),一旦它们被加载,回调函数中我们就可以直接使用该模块的假名来引用它了( 本例中就是cookie,和Tree) .

  4. 匿名模块

  define 方法允许你省略第一个参数,这样就定义了一个匿名模块,这时候模块文件的文件名就是模块标识。如果这个模块文件放在a.js中,那么a就是模块名。可以在依赖项中用"a"来依赖于这个匿名模块。这带来一个好处,就是模块是高度可重用的。你拿来一个匿名模块,随便放在一个位置就可以使用它,模块名就是它的文件路径。这也很好的符合了DRY(Don't Repeat Yourself)原则。

AMD 使用斜杠分割的模块名而不是使用点分割模块名,从而模块路径可以更直接的映射到URL,并允许使用相对路径来引用其他模块。这使得树状文件夹结构也有了移植性. (一个包含一组模块的文件夹可以被移动,改名,而其所包含的模块文件的相对名称不会失效)

  下面的代码就定义了一个依赖于alpha模块的匿名模块:

    
    
define([ " alpha " ], function (alpha) { return { verb: function (){ return alpha.verb() + 2 ; } }; });

  5. 仅有一个参数的define

  前面提到,define的前两个参数都是可以省略的。第三个参数有两种情况,一种是一个JavaScript对象,另一种是一个函数

  如果是一个对象,那么它可能是一个包含方法具有功能的一个对象;也有可能是仅提供数据。后者和JSON-P非常类似,因此AMD也可以认为包含了一个完整的 JSON-P实现。模块演变为一个简单的数据对象,这样的数据对象是高度可用的,而且因为是静态对象,它也是CDN友好的,可以提高JSON-P的性能。考虑一个提供中国省市对应关系的JavaScript对象,如果以传统JSON-P的形式提供给客户端,它必须提供一个callback函数名,根据这个函数名动态生成返回数据,这使得标准JSON-P数据一定不是CDN友好的。但如果用AMD,这个数据文件就是如下的形式:

    
    
define({ provinces: [ { name: ' 上海 ' ,areas: [ ' 浦东新区 ' , ' 徐汇区 ' ]},{ name: ' 江苏 ' ,cities: [ ' 南京 ' , ' 南通 ' ]} // ..... ] });

  假设这个文件名为china.js,那么如果某个模块需要这个数据,只需要:

    
    
define([ ' china ' , function (china){ // 在这里使用中国省市数据 });

  通过这种方式,这个模块是真正高度可复用的,无论是用远程的,还是Copy到本地项目,都节约了开发时间和维护时间。

  如果参数是一个函数,其用途之一是快速开发实现。适用于较小型的应用,你无需提前关注自己需要什么模块,自己给谁用。在函数中,可以随时require自己需要的模块。例如:

    
    
define( function (){ var p = require( ' china ' ); // 使用china这个模块 });

  即你省略了模块名,以及自己需要依赖的模块。这不意味着你无需依赖于其他模块,而是可以让你在需要的时候去require这些模块。define方法在执行的时候,会调用函数的toString方法,并扫描其中的require调用,提前帮助你载入这些模块,载入完成之后再执行。这使得快速开发成为可能。需要注意的一点是,Opera不能很好的支持函数的toString方法,因此,在浏览器中它的适用性并不是很强。但如果你是通过build工具打包所有的 JavaScript文件,这将不是问题,构建工具会帮助你扫描require并强制载入依赖的模块。

  6. Dojo中的AMD

  Dojo 在三月初正式发布了1.6版本,其中一个重要的变化就是引入了AMD机制,取代了原来的dojo.provide和dojo.require方法。但是现在仍然保持了向后兼容性,你仍然可以用dojo.provide和dojo.require来定义和加载模块。需要注意的是:在 Dojo 1.6 中, 针对 AMD 的重构仍然属于一个过渡期的改动,用户自己开发的 AMD 模块还不能被 Dojo 的加载器和 Build 系统支持 . 1.6 中现有的编译系统对AMD的支持还非常局限。

  如果你自己开发了 AMD 格式的模块,并且你仍然在使用默认的 Dojo 同步模块加载器,那么你必须严格遵循 Dojo 模块的格式 ( 包括换行的格式 ) 来保证你自己的模块能够成功编译。总结起来有以下三点:

  • 用传统的方法 (dojo.require()/dojo.provide()) – 这些模块,只能被 Dojo 同步加载器 加载,但可以被 Dojo 编译系统(Build System )正确的编译
  • 用 Dojo 同步加载器来加载 AMD 格式 ( define ()) 模块 – 这些模块可以被正常的加载,并且可以被其他兼容 AMD 格式的加载器加载 . 现在虽然 Dojo1.6 还没有正式支持这种用法, 但在目前的 Dojo1.6 编译系统中,是可以正常工作的 . ( 前提是你必须严格遵循 Dojo 模块定义的代码规范 )
  • 使用第三方加载器来加载 AMD 格式( define ())模块 – 模块可以被正常加载,并且可以被其他加载器所使用 . 这些模块可以使用 RequireJS 或 Backdraft 提供的编译系统正常编译,但是 Dojo 还没有正式的测试过和其他加载器的兼容性 .

  以Calendar为例,用define方法来定义这个模块:

    
    
define( " dijit/Calendar " ,[ " dojo " , " dijit " , " text!dijit/templates/Calendar.html " , " dojo/cldr/supplemental " , " dojo/date " , " dojo/date/locale " , " dijit/_Widget " , " dijit/_Templated " , " dijit/_CssStateMixin " , " dijit/form/DropDownButton " ], function (dojo,dijit) { dojo.declare( " dijit.Calendar " ,[dijit._Widget,dijit._Templated,dijit._CssStateMixin],{...} ); return dijit.Calendar; } );

  可以看到,模块标识就是模块文件的路径,模块本身一般都是dojo.declare定义的类。Dojo1.6中的dojo和dijit命名空间下的模块均已经用AMD的形式进行了重构,但dojox下仍然延用了传统的dojo.provide和dojo.require形式。对AMD的引入是Dojo走向自动化包管理的重要一步,在后续文章中我们也将继续关注Dojo在这方面的进展。

  7. 结论

  AMD 规范是JavaScript开发的一次重要尝试,它以简单而优雅的方式统一了JavaScript的模块定义和加载机制,并迅速得到很多框架的认可和采纳。这对开发人员来说是一个好消息,通过AMD我们降低了学习和使用各种框架的门槛,能够以一种统一的方式去定义和使用模块,提高开发效率,降低了应用维护成本。



dojo.provide/dojo.require/dojo.declare

JavaScript工具包的发展让这种需求的满足成为了可能。dojo.declare/dojo.provide/dojo.require就是dojo提供的用于管理JavaScript代码的工具。

dojo.declare

dojo.declare命令创建一个JavaScript对象。比如:

dojo.declare("com.test.dialog1",[dijit.Dialog],{
        a: "hello",title: "test dialog"
    });

其中,“com.test.dialog1”是对象名,它继承了dijit.Dialog对象,因此是一个可自定义的网页对话框。它具有继承自dijit.Dialog的属性title,即出现在对话框上边栏的文字,以及自定义属性a。属性a和title被封装在对象com.test.dialog1中,无法被全局访问。
dojo.declare 把类注册到你所指定的名字空间中,并且能够继承任意数量的类(由第二个参数传入)。

dojo.provide

创建了一个个JavaScript对象,那如何让别的JavaScript代码和HTML页面知道它们呢?这个时候dojo.provide就派上用场了。在如下的代码中:

dojo.provide(“com.test.dialog1”);
dojo.declare("com.test.dialog1",title: "test dialog"
    });

语句dojo.provide(“com.test.dialog1”)包含了多种含义:
1. 上述JavaScript代码被放置在文件夹/com/test/下的dialog1.js文件中;
2. 此dialog1.js文件以唯一命名“com.test.dialog1”暴露于其他JavaScript代码和HTML页面。

dojo.require

有了前面dojo.provide开放的JavaScript资源,dojo.require就是引用它们的方法。一旦在页面中调用:
dojo.require(“com.test.dialog1”);
dojo.require也有多种功用:
1. 声明js代码之间的依赖关系;
2. 加载相应的js文件dialog1.js到客户端。


可以看到,模块标识就是模块文件的路径,模块本身一般都是dojo.declare定义的类。Dojo1.6中的dojo和dijit命名空间下的模块均已经用AMD的形式进行了重构,但dojox下仍然延用了传统的dojo.provide和dojo.require形式。对AMD的引入是Dojo走向自动化包管理的重要一步.

版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。

相关推荐


我有一个网格,可以根据更大的树结构编辑小块数据.为了更容易知道用户保存了什么,我希望当用户第一次看到网格时,网格处于不可编辑状态.当用户准备好后,他们可以单击编辑按钮,这将使网格的某些部分可编辑.然后,有一个保存或取消按钮可以保存更改或还原.在大多数情况下它是有效的.但
我即将开始开发一款教育性的视频游戏.我已经决定以一种我可以轻松打包为Web,Mobiles和可能的Standalone版本的方式来实现这一目标.我不想使用Flash.因此,我确信(无论如何我会听取建议)使用JavaScript和SVG.我正在对这个问题进行大量研究,但我很难把各个部分放在一起.我知道Raphae
我正在使用带有Grails2.3.9的Dojo1.9.DojoNumberTextBox小部件–我在表单中使用–将固定格式(JavaScript基本格式)的实数值(例如:12.56)设置为HTML表单输入字段(但根据浏览器区域设置显示/编辑它们,所以用户总是看到格式正确的数字).另一方面,Grails期望输入字段根据浏览器
1.引言鉴于个人需求的转变,本系列将记录自学arcgisapiforjavaScript的学习历程,本篇将从最开始的arcgisapiforjavaScript部署开始,个人声明:博文不在传道受业解惑,旨在方便日后复习查阅。由于是自学,文章中可能会出现一些纰漏,请留言指出,不必留有情面哦!2.下载ArcGISforDe
我正在阅读使用dojo’sdeclare进行类创建的语法.描述令人困惑:Thedeclarefunctionisdefinedinthedojo/_base/declaremodule.declareacceptsthreearguments:className,superClass,andproperties.ClassNameTheclassNameargumentrepresentsthenameofthec
我的团队由更多的java人员和JavaScript经验丰富组成.我知道这个问题曾多次被问到,但为了弄清楚我的事实,我需要澄清一些事情,因为我在客户端技术方面的经验非常有限.我们决定使用GWT而不是纯JavaScript框架构建我们的解决方案(假设有更多的Java经验).这些是支持我的决定的事实.>
路由dojo/framework/srcouting/README.mdcommitb682b06ace25eea86d190e56dd81042565b35ed1Dojo应用程序的路由路由FeaturesRoute配置路径参数RouterHistoryManagersHashHistoryStateHistoryMemoryHistoryOutletEventRouterContextInjectionOutl
请原谅我的无知,因为我对jquery并不熟悉.是否有dojo.connect()的等价物?我找到了这个解决方案:http:/hink-robot.com/2009/06/hitch-object-oriented-event-handlers-with-jquery/但是没有断开功能!你知道jquery的其他解决方案吗?有jquery.connect但这个插件在我的测试中不起作用.
与java类一样,在dojo里也可以定义constructor 构造函数,在创建一个实例时可以对需要的属性进行初始化。//定义一个类mqsy_yjvar mqsy_yj=declare(null,{     //thedefaultusername    username: "yanjun",          //theconstructor   
我一直在寻找一些最佳实践,并想知道Dojo是否具有框架特定的最佳实践,还是最好只使用通用的Javascript标准?特别是我主要是寻找一些功能和类评论的指导方针?解决方法:对于初学者来说,这是项目的风格指南:DojoStyleGuide
我有’05/17/2010’的价值我想通过使用dojo.date.locale将其作为“2010年5月17日”.我尝试过使用dojo.date.locale.parse,如下所示:x='05/17/2010'varx=dojo.date.locale.parse(x,{datePattern:"MM/dd/yyyy",selector:"date"});alert(x)这并没有给我所需的日期
我正在尝试创建一个包含函数的dojo类,这些函数又调用此类中的其他函数,如下所示:dojo.provide("my.drawing");dojo.declare("my.drawing",null,{constructor:function(/*Object*/args){dojo.safeMixin(this,args);this.container=args[0];
我知道你可以使用jQuery.noConflict为jQuery做这件事.有没有办法与Dojo做类似的事情?解决方法:我相信你可以.有关在页面上运行多个版本的Dojo,请参阅thispage.它很繁琐,但似乎是你正在寻找的东西.一般来说,Dojo和jQuery都非常小心,不会破坏彼此或其他任何人的变量名.
我有一个EnhancedGrid,用户经常使用复杂的过滤器.有没有办法允许用户保存或标记过滤器,以便将来可以轻松地重新应用它?我知道我可以通过编程方式设置过滤器,但我无法预测用户想要的过滤器.谢谢!编辑:自己做了一些进展…使用grid.getFilter()返回过滤器的JSON表示,然后使用json.strin
我有这个代码:dojo.declare("City",null,{constructor:function(cityid,cityinfo){}});dojo.declare("TPolyline",GPolyline,{constructor:function(points,color){},initialize:function(map){});应该是什
我遇到的问题是我的所有javascript错误似乎来自dojo.xd.js或子模块.我正在使用chrome调试器和许多dijit功能,如dijit.declaration和dojo.parser.这有点烦人,因为它很难找到简单的错误或滑倒.我希望我可以添加一个选项,允许我的调试器在我的非dojo代码中显示选项会发生的位置.我是
我正在使用DojoToolkit数字/解析函数来处理格式化和使用ICU模式语法解析字符串.有没有人知道有可能采取任意ICU模式字符串并以某种方式使用Dojo(或其他)库将其分解为它的部分(例如,对于数字模式,它可以被分解为小数位数,数千个分组等…).我希望这样做,而不需要让我的代码密切了
我有两个看似相关的问题,访问在不同的地方定义的javascript函数.我遇到的第一个问题是调用我在firgbug或safari控制台中定义的函数.我定义了一个名为getRed的函数,如下所示:functiongetRed(row,col){//dosomethingstuffandreturntheredvalueasa
我想添加一个在Ajax调用中指定的外部样式表.我已经找到了一种方法来使用jQuery(参见下面的示例),但是我需要使该方法适应dojoJavaScript框架.JQuery示例$('head').append('<linkrel="stylesheet"type="text/css"href="lightbox_stylesheet.css">');谢谢.解决方法:一旦你
我正在尝试使用dojo.connect将onMouseDown事件连接到图像,如:dojo.connect(dojo.byId("workpic"),"onMouseDown",workpicDown);functionworkpicDown(){alert("mousedown");}类似的代码几行后,我将onMouse*事件连接到dojo.body确实完全正常工作.但是当我点击图像时