Dojo 工具箱提供各种特性支持 web 应用程序开发人员创建 Rich Internet Applications,既节约开发时间也省去麻烦。从 DOM 帮助程序和 Asynchronous JavaScript and XML (Ajax) 到全面成熟的小部件库和面向对象特性,Dojo 实际上包括构建大规模 Ajax 驱动的 web 应用程序所需的一切。如果您所需的功能不包含在 Dojo 之中,那么可能在 DojoX 中可以找到,DojoX 是一个扩展特性和实验性特性库,其中有工具箱 Base 或 Core 模块中不包括的那些特性。在这个关于使用 Dojo 工具箱开发富 web 应用程序的三部分系列的第 2 部分中,您将了解到 JavaScript 的面向对象特性,以及它们与传统的基于类的面向对象编程语言有何不同。您还将看到 Dojo 如何通过提供自己的一个基于类的系统来弥补这个差距。
什么是面向对象开发?
面向对象编程(Object-Oriented Programming,OOP)是一个软件开发范式,它基于称为 “对象” 的数据结构的定义,由数据属性和函数组成。这些属性(成员变量)和函数(或方法)定义软件与那个对象可能进行的交互。OOP 的主要好处是,简化您的代码结构,有助于代码重用和维护。
“面向对象” 基础知识
面向对象编程的基本前提是:在您的软件中创建一些对象,这些对象定义一系列应用于该对象的属性和一系列能够检索或修改该对象的属性的方法或函数。car
可能是一个简单的对象示例。与一个car
关联的数据属性可能包括它的manufacturer
、model number
、registration number
、color
、cubic capacity
,等等。一个car
对象提供的方法可能包括accelerate
、brake
、change gear
、turn
、stop
等等。在 OOP 中,理念是您定义对所有汽车都通用的基本属性和方法,每辆汽车都将采用那个定义的形式,尽管每辆汽车都采用不同的值。本文稍后将介绍,软件开发中有不同的面向对象途径。
这在 Firebug 控制台中生成以下输出:10C500 travelling at 45 mph
。
继承性和多继承性
下面,创建您的自动挡子类,就像前面使用纯 JavaScript 那样,但这次是使用dojo.declare
函数(见清单 12)。
清单 12. 使用 Dojo 创建ATCar
子类
dojo.declare("ATCar",Car,{ accelerate: function(increment) { this.inherited(arguments); if(increment >= 10) this.increaseGear(); },decelerate: function(decrement) { this.inherited(arguments); if(decrement >= 10) this.decreaseGear(); } });
如清单 12 所示,子类中只提供所有被覆盖的或新的属性和方法(在本例中,您只是覆盖加速和减速方法以自动换挡)。Dojo 负责自动调用超类中的构造器。如果您需要添加一个构造器函数,可以向子类添加一个构造器函数,但您不必担心调用超类构造器,因为那将自动进行。您将注意到,在两个被覆盖的方法中,行this.inherited(arguments)
都被调用,这将调用超类中的相同方法。这将使您避免重新编写代码来执行实际加速,只需像自动挡汽车那样方便换挡即可。
我们看看这个新子类的运行情况(见清单 13)。
清单 13. 运行中的新子类
var myCar = new ATCar("10C500"); myCar.accelerate(30); myCar.accelerate(20); myCar.decelerate(5); console.log(myCar.reg_no+" travelling at "+myCar.current_speed+" mph in gear "+myCar.current_gear);
这将生成以下输出:10C500 travelling at 45 mph in gear 2
。
Dojo 还支持多继承性。多继承性允许一个子类从多个父类派生,从每个父类继承属性和方法。严格说来,只有一个父类被认为是超类(数组中的第一个),但每个父类的构造器都将被调用,调用顺序与这些父类在数组中的顺序一致。
为演示多继承性,我们以一个 Smartphone 为例,除了接打电话和收发文本消息外,它还有很多功能(见清单 14)。通常,它应该还有播放音乐、观看视频等功能。为简单起见,我们假设一个 Phone 能打电话,一个 MediaPlayer 能播放视频,而一个 Smartphone 具有上述两个功能。
清单 14. Dojo 中的多继承性
dojo.declare("Phone",{ phone_number: "",minutes_remaining: 0,constructor: function(properties) { this.phone_number = properties.phone_number; this.minutes_remaining = properties.minutes_remaining; console.log("Phone "+this.phone_number+" powered on. You have "+this.minutes_remaining+" minute(s) remaining."); } }); dojo.declare("MediaPlayer",{ disk_space: 0,songs:[],constructor: function(properties) { this.disk_space = properties.disk_space; this.songs = properties.songs; console.log("Media Player powered on. You have "+this.songs.length+" songs,with "+this.disk_space+" GB free space left."); } }); dojo.declare("Smartphone",[Phone,MediaPlayer],{ phone_id: "",constructor: function(properties) { this.phone_id = properties.phone_id; console.log("Smartphone ID "+this.phone_id+" boot up complete."); } }); var songs = [ {artist:"U2",title:"Vertigo"},{artist:"Coldplay",title:"Yellow"} ]; var myPhone = new Smartphone({ phone_number:"(555) 123-4567",minutes_remaining: 60,disk_space: 2.5,songs: songs,phone_id: "4345FDFD7JAPO76" }); console.log(myPhone);
这里值得指出的第一点是:dojo.declare
是如何实现多继承性的。如您所见,一组类被传递,而不只是将父类作为第二个参数传递。这些父类的构造器将以它们在数组中的顺序自动被调用。重要的是要注意,如果每个父类构造器都接受不同的参数,那么 Dojo 将不能区分应该传递给每个构造器函数的参数。因此, 如果您需要将不同的参数传递给不同的构造器,您应该在简单 JavaScript 中以 “键/值” 对的形式添加参数并在构造器中以那种方式使用它们。
清单 15. 多继承性示例输出
Phone (555) 123-4567 powered on. You have 60 minute(s) remaining. Media Player powered on. You have 2 songs,with 2.5 GB free space left. Smartphone ID 4345FDFD7JAPO76 boot up complete. Object { phone_number="(555) 123-4567",more...}
单击最后一行中的链接将显示myPhone
对象的属性,如图 3 所示。
图 3. 从多个父类继承而来的myPhone
对象
在图 3 中,不同类的不同属性均出现在 Smartphone 类的实例化对象中。Phone
类的phone_number
和mintues_remaining
属性在那里,来自MediaPlayer
类的disk_space
和songs
在那里,来自Smartphone
子类的phone_id
成员变量也在那里。如果这些类有一些方法,那些方法也应该在那里显示。
dojo.mixin来改进多继承性示例
Dojo 提供了一个不错的工具函数dojo.mixin
,它允许您通过从左到右合并对象属性来混合对象(见清单 16)。
清单 16. 一个基本 dojo.mixin 示例
var objA = { a: 1,b: 2 }; var objB = { b: 3,c: 4 }; dojo.mixin(objA,objB); console.log(objA);
将objB
混合到objA
中之后,objA
中的属性将如图 4 所示。
图 4. 使用dojo.mixin
混合对象
最初在objA
中被设置为2
的b
属性已经被来自objB
的值3
所覆盖。而且,c
属性已经被添加。这个基本示例完成后,我们来看看如何在您的多继承性示例中使用dojo.mixin
。
在上一个示例中创建Phone
类时,您可能会回想起清单 17 中那个类的构造器中的两行。
清单 17.Phone
类构造器中的行
this.phone_number = properties.phone_number; this.minutes_remaining = properties.minutes_remaining;
由于只有两行,这还不太麻烦,但如果行比较多该怎么办呢?必须以这种方式分配属性难道不是一件很令人痛苦的事吗?而这正是dojo.mixin
函数真正有用的地方!使用下面的行替换这两行(以及MediaPlayer
和Smartphone
类中类似的行):dojo.mixin(this,properties);
。
结果与以前完全相同,但已经被传递到构造器的各个属性不会出现混乱。这很简洁,不是吗?
Dojo 中的打包和模块化开发
开发大型应用程序时,您很可能需要使用带有许多成员变量和方法的类。如果您来自一个 Java 开发环境,您可能更愿意遵循以下理念:不同的类应该驻留在不同的文件中,按照包进行分组。然后,当继承或其他目的需要时,再 “导入” 类,以确保它们仅在必要时才被加载。使用 JavaScript 时,没有这样的开箱即用打包和模块系统,但幸运的是,Dojo 提供了一个解决方案。
例如,在Car
类示例中,使用 Java 代码时您可能将该类存储在一个包中,如清单 18 所示。
清单 18. 在 Java 编程中打包类
package com.ibm.developerworks.dojoseries; public class Car { //Car class code goes here }
稍后可以将这个类导入其他 Java 类中,如清单 19 所示。
清单 19. 在 Java 编程中导入类
package com.ibm.developerworks.dojoseries; import com.ibm.developerworks.dojoseries.Car; public class ATCar extends Car { //ATCar class code goes here }
Dojo 通过dojo.provide
和dojo.require
函数提供了一个类似的打包系统。我们看看清单 19 中的 Java 代码在 Dojo 中是什么样子。首先,我们看看清单 20 中的Car
类。
清单 20. 在 Dojo 中打包类
dojo.provide("com.ibm.developerworks.dojoseries.Car"); dojo.declare("com.ibm.developerworks.dojoseries.Car",{ //Car class code goes here });
您可能已经注意到,这与 Java 代码非常相似,尽管类的整个包路径在dojo.provide
语句中提供,而不只是到包含包的路径。包路径很重要,因为它还决定当 Dojo 试图使用dojo.require
加载这个类时在哪里寻找它。因此,对于清单 20 中的示例,Car.js 文件应该存储在相对路径 com/ibm/developerworks/dojoseries/Car.js 中。如果它没有存储在那个位置,那么 Dojo 在需要它时就不能正确加载它。下面,我们看看如何导入这个类并从它创建一个子类(见清单 21)。
清单 21. 在 Dojo 中导入类
dojo.provide("com.ibm.developerworks.dojoseries.ATCar"); dojo.require("com.ibm.developerworks.dojoseries.Car"); dojo.declare("com.ibm.developerworks.dojoseries.ATCar",com.ibm.developerworks.dojoseries.Car,{ //ATCar class code goes here });
您将注意到,这里再次使用 dojo.provide 语句来确定这个类的加载路径。这个特殊的类将被存储在相对路径 com/ibm/develoeprworks/dojoseries/ATCar.js 中。然后,您使用dojo.require
来加载Car
类 — 使用它的完整包路径。最后,您声明这个子类,将完整路径作为第二个参数传递给其父类。由于这个类现在还没有被 Dojo 加载,因此它在 DOM 中可用且可以通过它的名称直接加载,不需要被放置到一个字符串中。
尽管从技术上讲类名可以与dojo.provide
语句中提供的路径不同(注意,使用dojo.require
进行的任何类加载必须使用dojo.provide
中设置的完全限定路径),但我们强烈建议不要这样做,因为这样只会导致混乱。
下一节解释如何使用 Dojo 的构建系统来打包您的应用程序,以确保它实现最佳性能和速度。
全部打包 — 使用 Dojo 的构建系统
使用 Dojo 的对象方向特性时,很有可能需要将您的类分隔到一些不同的文件中,以便使您的代码组织和管理任务变得更轻松。但是,重要的是要注意,加载许多 JavaScript 小文件可能会对您的应用程序造成严重的性能影响。每当一个 web 浏览器需要下载和执行一个 JavaScript 文件时,它都必须发送一个单独的 HTTP 请求,等待服务器做出响应,然后处理响应。因此,加载一个大文件通常比加载许多小文件更快。
使用一个大文件的问题是这可能是一个版本控制和代码组织噩梦,更不用说它可能包含您的应用程序实际上不需要的大量代码了。这个问题的解决方案就是在小文件大小和最小化您的应用程序发出的 HTTP 请求的数量之间找到适当的平衡。
这正是 Dojo 的构建系统发挥作用的地方,它允许您定义一些层,每一层都合并来自几个 JavaScript 源文件的源代码并缩小生成的单个文件, 从而保持文件大小最小。这个缩小过程能够极大地缩小您的代码,方法有二:一是移除所有不必要的空白和注释;二是将本地变量重命名为更短的名称并尽可能重构它们在函数中的使用。通过使用这个构建系统,可以使您的源代码组织良好便于开发,但是在部署到生产环境时要确保它以最优的性能水平运行。
使用 Dojo 构建系统的一个指南可能需要一篇单独的文章。幸运的是,Dojo 文档全面覆盖了这个主题。关于如何使用这个构建系统的更多信息,请参阅参考资料。
结束语
在这个关于使用 Dojo 工具包开发基于 web 的富应用程序的三部分文章系列的第 2 部分中,您学习了对象方向的基础知识,了解了 JavaScript 如何使用一个基于原型的 OOP 方法,并认识到许多传统开发人员长期持有的、关于 JavaScript 不是一个强大的支持 OOP 的语言的认识是错误的。然后,您了解了 JavaScript 的面向对象特性,如何定义对象原型,以及如何实现继承性。您发现了如何使用 Firebug 来测试 JavaScript 代码而不必将代码保存到一个文件中。接下来,您了解了如何使用 Dojo 的dojo.declare
函数来编写拥有 Java 开发人员熟悉的风格的 JavaScript 类。您还了解了如何执行继承性和多继承性,以及dojo.mixin
如何使合并对象属性成为小菜一碟。您还了解到 Dojo 的包和模块函数如何将您的类分隔到单独的源文件中,这与您在传统 OOP 语言(比如 Java 语言和 C++)中所做的非常类似。最后,您发现,通过在生产环境中最小化文件大小和 HTTP 请求,Dojo 构建系统帮助您以一种促进整洁的代码组织而不会影响性能的方式进行开发。
在这个系列的第三个、也是最后一个部分中,您将看到 Dojo 的小部件平台 Dijit 如何用于创建交互式富互联网应用程序。Dijit 构建于 Dojo 的面向对象特性之上,因此您在本文中所学到的知识将在下一部分中派上用场。
下载
描述 | 名字 | 大小 |
---|---|---|
文章源代码 | dojo2.source.zip | 2KB |
原文来自:http://www.ibm.com/developerworks/cn/web/wa-ground2/index.html
原文链接:https://www.f2er.com/dojo/290961.html