Dojo 框架:误解与现实

阅读数:5620 2010 年 12 月 23 日 00:00

随着 Ajax 技术的流行,越来越多的 Web 应用使用 Ajax 技术来提高用户体验。使用 Ajax 技术的一个重要优势是不需要额外的浏览器插件支持,只需要使用浏览器原生的 API,并利用 JavaScript 来操作即可。使用原生 API 时会遇到的两个比较大的问题是浏览器兼容性和底层 A 代 PI 接口带来的编程复杂性。同样的功能在不同的浏览器上的实现方式是存在差异的。如果一个应用希望支持不同的浏览器,则开发人员需要添加很多的浏览器检测或嗅探的代码。比如同样的事件绑定功能,在 IE 上使用 attachEvent ,而在其它浏览器上则使用 addEventListener 。除了兼容性问题之外,浏览器提供的原生 API 的接口一般都比较适合用来执行细粒度的操作。当需要完成一些相对复杂的操作的时候,使用原生 API 接口会使得代码比较繁琐。以一个 DOM 查询为例:在当前文档树中查找给定 ID 的节点的所有给定标签的直接子节点。对于这样一个查询,使用原生 DOM API 的话,则会需要使用 getElementById 来查找节点,通过 childNodes 来获取子节点列表以及比较节点的标签名称等。所要求的码量会比较大。

JavaScript 框架的出现,正是为了解决这两个比较大的问题,而不同的 JavaScript 框架也提供了各自额外的附加价值。目前可以使用的 JavaScript 框架非常之多,比较流行的也有十多种。这些流行的JavaScript 框架包括 jQuery Dojo YUI MooTools Prototype Ext JS Google Closure 等。这些不同的框架有着各自不同的优势和不足,也有着对应的不同的适用情景和范围。由于工作的关系,笔者对 Dojo 框架的使用最多,对于其它框架也有一定的了解。本文的目的是希望澄清一些对于 Dojo 框架的误解,从而帮助开发人员选择合适的框架。

在开始之前,首先简要介绍一下 Dojo 框架的基本结构。Dojo 框架由四个部分组成:Dojo 基本库、核心库、Dijit 和扩展库。基本库包含最基本的功能,核心库是基本库的扩展,Dijit 是用户界面库,而扩展库则是各式各样的扩展组件。

满足 Ajax 应用开发基本的需求

Dojo 和其它框架一样,都试图满足 Ajax 应用开发中的最基本的需求。这些基本的需求包括前面提到的浏览器兼容性和原生 API 的接口粒度问题,以及一些典型的应用场景。具体来说,应该包括下面一些功能集:JavaScript 语言增强、XMLHttpRequest 封装、DOM 查询与操作和事件处理等。而浏览器兼容性体现在这些功能集在不同浏览器上的效果是一样的。

从 Dojo 框架来说,对这些功能集的支持是比较丰富的。在 JavaScript 语言增强方面,对数字、字符串、日期类型、数组和 JavaScript 方法等有很多的增强功能。在 I/O 传输方面,除了常用的 XMLHttpRequest 之外,还支持 iframe 和 <script> 等。dojo.query 提供了类似于 jQuery 的 DOM 查询和操作的能力。 dojo.connect 不但可以用来绑定 DOM 元素上的事件,还可以连接到 JavaScript 方法的执行上。

上面提到的这些基本功能都出现在 Dojo 基本库和核心库中。打包压缩之后的代码大小在 80K 左右,不会对整个页面的代码量造成很大的影响。

面向对象 JavaScript 与函数式 JavaScript

面向对象的编程思想是目前比较流行的一种编程方法学。这种编程思想也被主流的编程语言所支持,包括 Java、C++ 和 C#等。很多开发人员都习惯于用面向对象编程思想中的类和对象的概念去进行分析和设计,再用相应的编程语言来完成实现。面向对象编程思想中的封装、继承和多态等概念,也比较适合对真实的问题域进行分析和抽象。在某些 Ajax 应用中,前端部分的逻辑比较复杂,同时也需要实现一部分业务逻辑。所需的代码量已经不是几个简单的方法这个级别,而需要进行完整的建模、分析和设计。很多开发人员会自然而然的使用面向对象的编程思想对 Ajax 应用的前端进行分析和设计。但是 JavaScript 语言并不是一门面向对象的编程语言,它在很多方面都不同于传统的面向对象编程语言。因此在从分析和设计到实现的过程中,会出现阻抗不匹配的情况。对于这种不匹配的情况,解决的办法不外乎两种:一种是改变分析和设计时的思路,而另外一种则是对JavaScript 语言进行面向对象方面能力的增强。

JavaScript 语言在设计的时候,就带有一些面向对象编程语言的影子,如 new 操作符和通过原型(prototype)可以实现的继承机制等。通过 JavaScript 的这些语言特性,可以实现完整的面向对象能力。Dojo 框架所提供的面向对象方面的能力非常完备。最典型的用法是可以通过 dojo.declare() 方法来声明一个类,并且可以支持多继承。使用 Dojo 可以很快的写出经典的支持多继承的面向对象的示例,如:

dojo.declare("Human", null, {
  think : function() {}
});
dojo.declare("Machine", null, {
  work : function() {}
});
dojo.declare("Robot", [Machine, Human], {
  turingTest : function() {}
});

Dojo 通过其面向对象 JavaScript 的支持,在一定程度上解决了前面提到的阻抗不匹配的问题。但是在全面使用 Dojo 提供的面向对象 JavaScript 的能力的时候,要注意会带来的一些风险。首先要理解的是函数在 JavaScript 中是一等公民,可以作为对象的属性以及函数的参数和返回值来使用。 JavaScript 中的闭包也是一个非常强大的概念,妥善使用的话可以写出简洁而强大的程序。如果完全按照 Dojo 所抽象出来的面向对象的方式来使用 JavaScript,会丢失掉 JavaScript 语言本身的一些好的特质。所以不建议开发人员一开始就深入到 Dojo 的面向对象 JavaScript 的世界里面,而是首先多了解一些 JavaScript 语言本身的特征。比如理解 JavaScript 中的原型链(prototype chain)、this 的含义、new 操作符、执行上下文(execution context)和作用域链(scope chain)等。否则的话,一旦形成了思维定势,可能会无法理解其它框架或是库的实现方式,毕竟不是所有的库都采用了 Dojo 这样的方式来实现。其次要认识到在性能方面可能带来的影响。熟悉 Java 的开发人员可能都习 惯于在设计的时候使用很多个细分的 Java 类,这些类之间通过精细的协作来完成具体的任务。这对 Java 来说是合理的。而对于运行在浏览器中的 JavaScript 这种解释执行的语言来说,过多的对象和消息传递会对性能造成一定的影响,而性能又是 Ajax 应用中需要关注的重要因素。

面向对象编程的思想进入到 Web 应用的前端开发领域,是一件好的事情。它使得广大前端开发人员可以用自己熟悉的方式来设计和开发 Web 前端。但是在 JavaScript 语言本身和浏览器这个运行平台的双重限制下,需要适度的使用,不过未来的前景是乐观的。JavaScript 是 ECMAScript 的方言之一,目前的实现基本都遵循的是 ECMAScript 第三版规范。而 ECMAScript 第五版规范已经发布。值得一提的是,ECMAScript 第四版尝试在 JavaScript 中引入类、包和名称空间等概念,不过由于各种原因被放弃了。而第四版的这些思想形成了新的正在开发中的 ECMAScript Harmony 项目。按照标准化过程的速度,短期之内 JavaScript 语言是不会拥有传统面向对象编程语言的类的结构,而包和名称空间的结构则需要等待更长的时间。不过这一天终会到来。随着 V8 Chakra SquirrelFish Carakan 等新的 JavaScript 引擎的出现,JavaScript 语言本身的执行性能将会有大幅度的提升。这两个方面的改进会使得以面向对象的思想编写 JavaScript 程序变得更加自然。

Dojo 的复杂度过高

Dojo 是一个庞大和复杂的库,其中包含数以百计的模块。每个模块都有自己的源代码、测试用例、演示页面和文档说明等。从这个角度来说,Dojo 的复杂度高于 jQuery 等其它框架。对于 Ajax 应用来说,有两种常见的风格:Ajax Lite 和Ajax Deluxe。对于Ajax Lite 风格的Ajax 应用来说,jQuery 等轻量级框架是比较好的选择,可以很方便的对页面做出修改。只使用Dojo 基本库也是不错的选择。对于Ajax Deluxe 风格的应用来说,Dojo 可以体现出它的价值。在开发风格的复杂Ajax 应用时,一套完整的用户界面组件库是非常有必要的,可以极大节省开发人员的时间。在这个层次上,Dojo 和jQuery 采用了不同的做法。jQuery 非常小巧灵活,暴露给开发人员的概念非常少。$、CSS 选择器和方法级联,就已经差不多是全部了。社区也贡献了非常多的 jQuery 插件,丰富了jQuery 本身的功能。这是一种自下而上的做法,先有一个稳健的基础,再依靠社区的力量发展壮大。Dojo 的做法则正好相反。Dojo 中已经集成了很多模块,满足各种不同的需求。这些模块背后都体现了相同的设计思想。以用户界面与数据的关系为例,Dojo 定义了 dojo.data API 来抽象异构数据源的访问接口。需要访问数据的用户界面组件都通过此 API 来访问数据。这种做法带来的问题是暴露给开发人员的概念过多,给开发人员的感觉是完成一件简单任务的起步就非常困难。不过这种做法也为框架本身的维护和扩展带来了方便。当构建一个复杂的 Ajax 应用的时候,这种复杂性有时候是非常必要的,尤其在团队工作的时候。对于一个复杂的问题,总是会需要一些稍微复杂的设计来保证解决方案的可维护性。与其选择自己去处理它,还不如交给一个设计良好的框架来完成。

对于开发 Ajax Lite 风格的 Ajax 应用来说,也可以从 Dojo 基本库开始。当需要的时候再考虑 Dijit 库。

Dojo 不易上手,学习曲线较陡

前面提到了 Dojo 的复杂性,这种复杂性会使得开发人员很难在较短的时间内入门。开发人员要理解和接受的概念过多。Dojo 框架本身也提供了两种类型的编程风格,即前面提到的面向对象和函数式的方式。Dojo 基本库和核心库比较多的采用的函数式的风格,比如 dojo.connect()、dojo.xhrGet、 dojo.declare() 和 dojo.query() 等。开发人员可以把这些方法当成工具来使用。对于 Dojo 基本库和核心库来说,只需要查看相关的 API 说明文档就可以知道每个方法的参数、返回值和需要注意的地方。Dijit 库则使用的是面向对象的风格。Dijit 库包含的是一些用户界面组件,组件内部封装了相关的逻辑。开发人员需要通过 new 操作符来在页面上创建出组件的实例。这样的使用方式对熟悉 Java 图形界面组件库,如 SWT/JFace 和 Swing 的开发人员来说,是比较好理解的。而两种编程风格杂糅在一起,会对开发人员的理解造成一定的问题。在这点上, jQuery UI 的做法就更加可取一些。在 Dijit 里面创建一个对话框并打开的做法是:

var dialogNode = dojo.query("#dialogNode")[0];
var dialog = new dijit.Dialog({});
dialog.open();

而在 jQuery UI 里面,使用的方式是:

$("#dialogNode").dialog({autoOpen : false});
$("#dialogNode").dialog("open");

jQuery UI 在编程风格上与 jQuery 是相似的,采用的都是函数式的风格。这种一致性对开发人员来说是更加合适的。

用户界面组件

在 Ajax 应用的前端界面部分,少不了用户界面组件的支持。HTML 语言本身提供了一些基本的元素,包括常见的 div、span 和表单元素等。使用这些基本元素可以构造出复杂的用户界面。但是相对于桌面应用开发时可以使用的组件来说,HTML 语言的这些元素还是过于基本,无法快速高效的进行开发。比如一些常见的界面组件,如菜单、对话框、树形控件、表格控件、日期和时间选择器和富文本编辑器等,都需要开发人员自己来实现,不仅耗时而且质量也比较难以保证。对于这种情况,Dojo 框架提供了自己的用户界面组件编程模型 Dijit,以及一些高质量可定制的标准用户界面组件。通过使用和定制这些标准组件,可以很快速的构建出应用的界面。开发人员也可以根据编程模型,开发出自己应用所需的特有组件。

从这个角度来说,Dojo 框架希望提供的是与桌面开发相似的用户界面组件库,比较适合在集成开发环境中使用。开发人员通过拖拽的方式来添加组件,并设置组件的相关属性。通过这种方式,可以帮助开发人员更快的构建复杂的 Ajax 应用。Dijit 库的好处在于提供了一个设计良好的 Web 应用前端组件编程模型,以及在这模型基础之上的众多参考实现。这就为创建一个良好的组件共享平台打下了基础。实际上,在 Dojo 扩展库中就已经有不少由社区贡献出来的组件。这个编程模型的一些优点在于:

  • 完整的生命周期管理。从创建到销毁,生命周期中的不同阶段都允许开发人员进行定制和扩展。
  • 基于 HTML 模板的方式快速创建用户界面。支持在模板中以声明式的方式绑定 DOM 元素和事件。
  • 统一的组件接口,包括属性设置和获取和事件绑定等。
  • 完善的主题支持,可定制的组件外观。

从前端开发人员的角度来说,如果对用户界面的组件化是一个必要的设计考虑,则 Dijit 是一个比较好的起点。

Dojo 的性能比较差

对于 Web 应用来说,性能是一个非常重要的因素。既然 JavaScript 库是目前 Ajax 应用开发中必不可少的一部分,那么性能方面的差别会成为选择的重要因素。一般对 Dojo 框架的认识是速度很慢。实际上,影响 Web 应用性能的因素非常多,包括 HTTP 请求的个数、请求响应内容的大小、JavaScript 代码的执行时间、页面元素的重新布局和排列次数等。把页面的速度过慢单纯归咎于 JavaScript 库本身,是有失偏颇的。

对于 Dojo 库的一个比较常见的看法是 Dojo 库过于庞大,需要加载比较多的资源文件,导致页面的加载速度过慢。确实,与 jQuery 和 Prototype 等 JavaScript 库相比,Dojo 库分发包偏大。Dojo 1.5 的分发包是 2M,而 jQuery 1.4.4 压缩之后的大小才 26K。不过两者的功能是不同的。Dojo 库所提供的功能更多,所包含的代码量自然更大。造成这一原因的问题在于开发环境和部署环境的不同。对于 jQuery 来说,开发环境和部署环境是相同的,只需要复制单个 JavaScript 文件即可。而对于 Dojo 则没有这么简单,这中间缺少的步骤是构建过程。

Dojo 采用的是模块化的设计,其中包含非常多的模块,分布在 Dojo 基本库、核心库、Dijit 库和扩展库中。通过 dojo.require 可以声明在页面中需要加载的模块。这个加载过程会需要从服务器端下载所需的 JavaScript 文件,从而导致在运行时过多的 HTTP 请求。Dojo 的构建系统会把来自不同模块的JavaScript 文件打包在一个文件中,只需要在页面上引用打包好的单个JavaScript 即可。使用Dojo 的构建过程,需要下载Dojo SDK,在utils/buildscripts/profiles 目录下面添加一个构建文件,如myDojo.profile.js。在该文件中声明所需要包含的模块,如:

dependencies = { 
    layers: [ 
       { 
           name: "dojo.js",
           dependencies: [ 
                "dijit.layout.BorderContainer",  
                "dijit.layout.ContentPane", 
                "dojox.layout.ExpandoPane", 
                "dojox.image.Lightbox" 
           ] 
       } 
    ], 

    prefixes: [ 
        [ "dijit", "../dijit" ], 
        [ "dojox", "../dojox" ] 
    ] 
} 

再通过运行

build profile=myDojo 

action=release 就可以启动构建过程,最后在 release 目录下面的就是可以直接复制到部署环境的 Dojo 库。Dojo 的构建过程使用的是运行在 Rhino 上的 JavaScript 代码,可以很好的与 Apache Ant 集成。也可以选择使用其它图形化构建工具,如: Dojo Toolbox

参考资料


感谢张凯峰的策划以及审校。

给 InfoQ 中文站投稿或者参与内容翻译工作,请邮件至 editors@cn.infoq.com 。也欢迎大家加入到 InfoQ 中文站用户讨论组中与我们的编辑和其他读者朋友交流。

评论

发布