08年到IBM实习的时候,第一次接触Dojo,被它强大的控件库所吸引,相比以前自己写的那些普通的HTML元素好看多了。而且Dojo提供的方便的接口也让我觉得控制一个控件还有如此简单的方法。Grid给我带来的震撼更是不言而喻,这么炫的一个表格竟然仅需要不多的几行代码就可以展现出来,还提供了freeze,edit,tooltip等扩展功能。所以官网上的Unbeatable JavaScript也给我烙下了深刻的印象。Dojo本身的面向对象的编程模式,也让习惯了写Java的我非常顺手,逐渐对前台,对Dojo产生了更深的兴趣。
然而随着使用的深入,很多Dojo与生俱来的问题给我带来了很多烦恼。
- 重量级框架,严重影响页面载入速度
- 加载渲染过程开销太大,严重影响页面展现速度,甚至出现浏览器假死
- 万恶的内存泄漏,消耗用户内存,页面越跑越慢
- DataGrid让我欢喜让我忧,很多不支持的功能,很多的bug,让开发人员欲仙欲死
- 页面风格的定制非常困难,需要覆盖太多的Dojo的style
- Build压缩中遇到各种问题
尽管遇到这些问题,但是作为IBM首选的前台框架,它依然在我参与的各个项目中有个不可动摇的地位。在摸透它的习性之后,便开始掌握和改造Dojo,不支持的功能自己扩展,不好的接口自己改造,没有的控件自己写。曾经为了使DataGrid支持整行编辑(类似于Ext Grid),更是不惜代价几乎重写了EnancedGrid,从_Layout到_Event,到_EditManager,到_ViewManager,到Pagination插件。。。在这个过程中,不断熟悉,不断深入,从代码中一步步学习到Dojo内部的实现,看到Dojo的一些JavaScript功能是如何被开发和使用的。如Dojo.connection,Dojo.subscribe,Dojo.publish。。也了解到了Dojo控件的生命周期,从constructor到buildRending到postCreate到startUp。。。
在这样的学习深入过程中,我一直也在判断,Dojo到底算不算一个很好的前台框架,带着这样的疑问,我尝试着去学习和使用其他的一些前台框架,如ExtJS和jQuery。同样是重量级框架的Ext,放弃了HTML作为显示基础,而纯粹使用confogjs写展示的方式让我很不适应。我觉得,页面显示的内容,布局,就应该有HTML来展现,这更符合Web本身的结构,也更能让人了解页面所要展现的内容。在与UX的同事交流的时候,没有了HTML,就相当于失去了交流的语言,给双方的合作带来了很大的负面影响。所以我坚信,ExtJS不是适合互联网开发的框架,当然Dojo也差不多。当我看到jQuery的$符号的时候,我知道,这才是现在互联网所需要的。于是我疯狂的学习jQuery,在学习过程中,不断的被jQuery的一些新的理念所折服:强大的选择器功能,方便的Dom操作接口,链式编程,百花齐放的插件。这些都太让我欣喜了,在学习的过程中,牵引出来的CommonJS项目,AMD加载方式,都让我看到了现在前端的发展趋势,看到了JavaScript新的春天。这些都让我一度想放弃深入Dojo而使用jQuery的念头,直到我再次接触到Dojo1.8。
在迫不得已写了一段时间ExtJS之后,旁边的项目组说要使用dojo,让我帮忙评估一下的时候我看到了dojo1.8,看到了从1.6到1.8之间的巨大的改变,看到了Dojo在追随前端趋势上所做的努力和成果。在看完新Feature之后,我把Documentation页面上的大部分的新的文章都读了一遍,这一遍让我对Dojo重新看到了希望。1.8的模块化,CND引入,AMD加载方式都让我看到了dojo成为一流框架,并继续引领前端开发的希望。因此我无比期待Dojo2.0的发布,相信那将是Dojo再次证明自己是Ubeatable的时候。
扯了这么多,突然发现这篇文章的前奏有点长了。。。 赶紧切入正题,这篇文章主要讲讲我看到的dojo从1.6到1.8的变化,主要结合翻译和理解Modern Dojo和Dojo1.x to 2.0 migration guide这两篇官网上的文章,讲讲Dojo1.8的新的方法,以及这些变化背后的意义。
今天有点晚了,待续。。。(12年12月18日)
1、AMD
其实dojo在1.7的时候就开始支持AMD(Asynchronous Module Definition)了,当时只知道是一种异步加载的方式,并未觉得比dojo.require()好多少,dojo.require()同样可以在需要的时候去动态加载文件。知道在关注CommonJS的时候,再次看到AMD,就明白了Dojo对AMD的支持是为了与CommonJS保持一致,提供统一的接口可以让它更流行。
那么AMD到底给dojo带来了哪些方面的变化,对于使用dojo的开发人员需要注意哪些呢?接下来从 加载Dojo(Loading Dojo),加载模块(Loading Module),访问模块(Accessing Module), 定义模块(Defining Module),全球化(I18N),加载Text和Template文件等方面对新的Dojo用法进行介绍。
加载Dojo(Loading Dojo)
Dojo的加载方式基本没有发生变化,除了将djConfig属性名字改成了data-dojo-config,这也是为了符合HTML5的自定义属性的规范。
<script src="dojo/dojo.js" data-dojo-config="async: true"></script> //这里的async:true可以控异步加载dojo core的代码
这里的dojo config也可以在加载dojo的script的外部定义,使用方法如下:
<script> dojoConfig= { has: { "dojo-firebug": true }, parseOnLoad: false, foo: "bar", async: true }; </script> <script src="//ajax.googleapis.com/ajax/libs/dojo/1.8.1/dojo/dojo.js"></script>
具体的dojoConfig里面的属性代表的含义,大家可以参考Configuring Dojo with dojoConfig这篇官网的文章,我后面有空的话也会进行翻译。
这里值得一提的是,在dojoConfig选项中的async,它默认为false,这意味着所有的Dojo基本模块是自动加载的,不管你用不用的到。所以将async设置成ture,将能提供一个更快更智能的动态加载的应用。
大家可以注意到上面代码的最后一行,dojo的代码是从远程的googlelieapis网站上去拿的,这也是dojo提供的CDN(Content Delivery Network,内容发布网络)入口。至于为何使用CDN,大家可以google一下,大概的好处就是1.各个不同的应用可以共享浏览器cache。2.可以减少自身服务器的请求和流量。 坏处当然也有:1.下载的代码一般都是压缩过的,开发人员很难以此进行阅读和调试。2.在没有网络的情况下,本机的开发就不能进行了。。
加载模块(Loading Modules)
在Dojo1.6及以前版本,加载一个模块都是使用dojo.require()。如:
dojo.require("dijit.form.Button"); dojo.require("dojox.layout.ContentPane"); ... // CODE HERE
而从1.8之后,就要使用require语句,如:
require(["dijit/form/Button", "dojox/layout/ContentPane", ...], function(Button, ContentPane, ...){ // CODE HERE });
要注意到之前的以类似与包名类名的方式(如dijit.form.Button)现在已经改成了路径的方式(如dijit/form/Button)。之前的基于点(.)的类名表达了一个全局作用域内的一个变量,而现在基于斜杠(/)的路径方式表达则是一个模块的ID(MID)。这两种方式尽管在命名上区别不大,但事实上在概念上是完全不同的东西。
使用require函数也同dojo.require一样,会帮我们自动去加载更深层次依赖。
访问/使用模块(Accessing Modules)
在上面的require代码里大家应该注意到,require有两个参数,第一个是要用到的模块,第二个则是一个匿名函数,这个匿名函数还带有一些参数。用法如下:
require(["dijit/_base/Color", "dojox/layout/ContentPane"], function(Color, ContentPane){ Color.fromRgb(...) });
在匿名函数里的参数就是我们在这个函数闭包内使用这个模块内的类或者方法的入口。我们将不在使用dojo,dijit或者dojox这些全局变量来访问。所有需要直接使用的模块都必须被定义在require的依赖数组当中。这些模块还将以缩写的方式座位函数的参数在函数内使用,免去了冗长的路径书写方式。
下面我们来讲一下这种新的使用方式的意义。
首先从AMD的角度,所谓异步加载,就是说加载这些模块的过程和整个应用的脚本的运行是异步的,即,加入这个require后面还有别的可执行代码,那么他可以立刻执行而不需要等到require所需要的模块都加载回来之后再被运行。作为require第二个参数的匿名函数将在require的所有的模块被加载完了之后被执行。这种新的方式下,原先被压缩在dojo.js中的很多模块将被独立成很多小的模块,从而减小dojo.js文件的大小。
从编码的角度,匿名函数的参数被定义为前面加载的模块的缩写,是为了在代码中更方便地访问和使用这些模块。
从作用域的角度,消灭了全局变量。将所有这些模块的使用封装在一个函数的作用域内,就减少了全局变量带来的冲突和不确定性。在Modern Dojo中有这样一句话“ in "modern" Dojo, if you are about to access something in the global namespace STOP because you are doing something wrong. If you find yourself typing dojo.*
or dijit.*
or dojox.*
, something isn't right.”,也就是说以后大家在使用dojo的时候,写的代码应该是Lang.hitch而非dojo.hitch,是new Button(),而非new dijit.form.Button()。
Dojo提供了在HTML中直接通过定义data-dojo-type来定义dojo widget的方式,在1.8中,将1.6中的的dojoType改成了data-dojo-type。同时,原先使用的dijit.form.Button的全局变量的方式也被改成了使用带斜杠的模块ID来定义。如:
<input type="text" data-dojo-type="dijit/form/TextBox"/>
定义模块(Defining Modules)
在Dojo1.6中我们使用dojo.provide()来定义自己的模块,使用的时候可以使用dojo.require()加载我们自定义的模块,如:
dojo.provide("acme.Dialog"); dojo.require("dijit._Widget"); dojo.require("dojo.date"); //CODE HERE
而Dojo1.8开始使用define函数来定义模块,如:
define(["dijit/_Widget", "dojo/date"], function(_Widget, date){ .... // CODE HERE return MyWidget; });
define方法与require方法类似,也是两个参数,一个是依赖关系的数组,另一个是真正要运行的匿名函数。这个函数的返回值将作为这个define的模块的主体。一般会和declare函数一起使用,如:
define(["dijit/_Widget", "dojo/date"], function(_Widget, date){ .... return declare("my.widget",[_Widget, date],{ // CODE HERE }); }); //当然也可以下面的写法 define(["dijit/_Widget", "dojo/date"], function(_Widget, date){ .... var myWidget = declare("my.widget",[_Widget, date],{ // CODE HERE }); return myWidget; });
I18N(国际化)
先扯一句题外话,I18N为什么叫I18N,其实是一个很有趣的理由。人们常把I18N作为“国际化”的简称,其来源是英文单词 internationalization的首末字符i和n。18为中间的字符数。
在Dojo1.6中使用的 dojo.requireLocalization()在Dojo1.8中被封装到了dojo/i18n!加载器插件当中。
以前的写法:
dojo.require("dojo.i18n"); dojo.requireLocalization("dijit.form", "validate"); var validate = dojo.i18n.getLocalization("dijit.form", "validate"); console.log(validate.invalidMessage);
将被改成:
require(["dojo/i18n!dijit/form/nls/validate"], function(i18n){ console.log(i18n.invalidMessage); });
这一篇重点介绍了Dojo1.8的AMD机制带来的一些变化,就先介绍到这里。下一篇会重点介绍1.8中Dojo core(Dojo核心代码)的结构和使用上的变化。