从头开始学习 Dojo,第 2 部分: 使用 Dojo 掌握面向对象开发

从头开始学习 Dojo,第 2 部分: 使用 Dojo 掌握面向对象开发

Dojo 工具箱提供各种特性支持 web 应用程序开发人员创建 Rich Internet Applications,既节约开发时间也省去麻烦。从 DOM 帮助程序和 Asynchronous JavaScript and XML (Ajax) 到全面成熟的小部件库和面向对象特性,Dojo 实际上包括构建大规模 Ajax 驱动的 web 应用程序所需的一切。如果您所需的功能不包含在 Dojo 之中,那么可能在 DojoX 中可以找到,DojoX 是一个扩展特性和实验性特性库,其中有工具箱 Base 或 Core 模块中不包括的那些特性。在这个关于使用 Dojo 工具箱开发富 web 应用程序的三部分系列的第 2 部分中,您将了解到 JavaScript 的面向对象特性,以及它们与传统的基于类的面向对象编程语言有何不同。您还将看到 Dojo 如何通过提供自己的一个基于类的系统来弥补这个差距。

Joe Lennon,首席移动开发人员,Core International

2012 年 1 月 30 日

  • 内容

什么是面向对象开发?

面向对象编程(Object-Oriented Programming,OOP)是一个软件开发范式,它基于称为 “对象” 的数据结构的定义,由数据属性和函数组成。这些属性(成员变量)和函数(或方法)定义软件与那个对象可能进行的交互。OOP 的主要好处是,简化您的代码结构,有助于代码重用和维护。

“面向对象” 基础知识

面向对象编程的基本前提是:在您的软件中创建一些对象,这些对象定义一系列应用于该对象的属性和一系列能够检索或修改该对象的属性的方法或函数。car可能是一个简单的对象示例。与一个car关联的数据属性可能包括它的manufacturermodel numberregistration numbercolorcubic capacity,等等。一个car对象提供的方法可能包括acceleratebrakechange gearturnstop等等。在 OOP 中,理念是您定义对所有汽车都通用的基本属性和方法,每辆汽车都将采用那个定义的形式,尽管每辆汽车都采用不同的值。本文稍后将介绍,软件开发中有不同的面向对象途径。

常用 OOP 术语

为最大限度地利用本文,您至少应该熟悉面向对象编程及其概念。下面简要描述讨论面向对象开发时使用的一些常用术语。需要说明的是,并非所有 OOP 类型都包含每个概念;例如,基于原型的对象语言(比如 JavaScript)中就没有 “类”。

在基于类的面向对象开发中,一个类定义组成一个对象的不同属性和函数。类定义用于生成对象的模板,因此它们应该定义这些对象能够遵守的公共属性和动作。类通常由成员变量和方法构成。

成员变量

对象的成员变量就是该对象的属性。在前面提到的汽车示例中,这些属性包括该汽车的manufacturermodelcubic capacity,等等。

方法

方法是对象能够执行的动作。例如,一辆汽车能够turn,等等。通常,方法将修改成员变量的值。例如,当一个car对象使用accelerate方法加速时,它的当前速度属性将增加。许多对象都有一个称为constructor的方法,该方法在对象创建后立即被调用。

实例或对象

实例或对象即实际对象本身,而不是用于定义对象的模板。例如,您可能有一个名为myCar的对象,它拥有一个汽车模板的属性和方法。在一个对象的实例中,属性将实际拥有值。例如,myCar可能拥有一个值为silver的 color 属性,一个值为2500的 cubic capacity 属性。一个对象的属性的当前值称为该对象的状态,该状态可以在该对象的整个生命周期内变化。

继承性

在基于类的 OOP 中,继承性是这样一个过程:子类继承其父类的成员变量和方法。除了继承这些属性和动作之外,子类可以定义自己的成员变量和方法,并提供父类的属性的默认值。例如,您可能有一个FourByFour类,它是Car类的一个子类。这个子类可以将其父类的 drivetrain 属性的默认值设置为4WD(四轮驱动)。另外,它可以定义另一个名为 transfer case 的属性,该属性仅适用 4x4 汽车;并定义一个方法,该方法允许您更改其他普通车辆上没有的低速档(low range gear)。

封装

在基于类的 OOP 中,成员变量通常被定义为私有变量,以免被从类本身的范围外访问或修改。有一些称为 “修改器” 的特殊方法,它们允许您定义可以检索或修改类中的私有成员函数的值的方法。这些方法(通常称为 getters 和 setters)允许程序员使用隐藏的信息,使应用程序和其他类只能访问某些属性。这种技术通常称为 “封装”。

抽象

抽象是通过只定义那些在您对象的当前上下文中对该对象很重要的属性和方法来减小对象的复杂性的过程。例如,当您定义一个Car类时,可以通过定义一辆汽车拥有的、对其他类型的车辆(比如有蓬货车、卡车、摩托车等)也常见的所有属性,来将这个类进一步抽象为一个Vehicle类。这样,Car类将从Vehicle类继承这些属性,就像一个Motorcycle类或Van类那样。

多态性

在 OOP 上下文中,多态性意味着可以从它的超类继承方法,而不必提供所有方法的相同实现。例如,您有两个Car类的两个子类,一个用于自动挡汽车(我们称其为ATCar),另一个用于手动挡汽车(我们称其为MTCar)。所有Car对象都能加速,因此ATCarMTCar都将从它们的父类继承accelerate方法。但是,在一个ATCar中,当引擎达到某个 RPM 级别时,您的 accelerate 方法将自动调用 change gear 方法。结果,您在ATCar子类中覆盖了accelerate方法的父类定义,而在MTCar中,该方法就是子类从Car类继承而来的方法。

本文并不是面向对象编程的一个全面指南。如果您不熟悉上述概念,那么在继续阅读本文之前,有必要跳到参考资料部分阅读更多关于 OOP 的资料。

回页首

面向对象的 JavaScript

上一小节介绍的许多概念都是称为基于类的面向对象编程的特定 OOP 范式所专用的。但是,并非所有编程语言都遵守这个范式。另一种常见 OOP 类型是基于原型的面向对象编程,JavaScript 语言中就使用该范式。在本小节中,您将了解 JavaScript 的面向对象实现,一个用例,以及您可能会遇到的一些障碍,特别是当您来自一个基于类的 OOP 环境时。

JavaScript 并不只是一种基本脚本语言

当 JavaScript 首次流行时,它主要用作一种在基本 web 页面上执行一些简单技巧的方法。大多数 JavaScript 用户都不是软件开发人员,他们是图形或 web 设计师,他们可能拥有丰富的 HTML 经验,但他们的编程语言知识很少,甚至没有。HTML 本身在支持生成动态效果方面的功能是非常有限的,而这正是 JavaScript 优势所在。然而,大多数设计师并不实际学习如何用 JavaScript 编程,而只是尽量找到它们需要的代码段,仅仅学习调试该代码段以满足自身需要的所需的知识,然后使用那段小代码。当设计师这样做过几次之后,他们就会误以为他们自己的 JavaScript 水平不错了。

在 web 早期,JavaScript 的功能很有限。但是,它现在已经成长为一个成熟的、功能齐全的编程语言,它不再只用于为网站编写一些简单技巧,而是用于驱动整个富互联网应用程序。事实上,作为一种语言,JavaScript 今天还在以各种方式应用。例如,CouchDB 面向文档数据库管理系统就使用 JavaScript 函数来查询数据库中的数据。

许多 Java 开发人员(以及使用其他更传统的编程语言的开发人员)可能会轻视 JavaScript,认为它是一种基本脚本语言。尽管 JavaScript 已经发展得非常强大,认为它只是用于在网站上执行一些小技巧的误解仍然存在。这是因为大多数使用 JavaScript 的 web 开发人员将使用 jQuery、Prototype 或 Dojo 这样的库来避免为它们编写 JavaScript 的工作。事实上,很多 web 开发人员可能被视为编写 jQuery 应用程序的专家,但对于 JavaScript 本身,他们实际上几乎没有什么专长。很多这样的开发人员没有意识到的是,JavaScript 实际上非常强大,且包含开箱即用的面向对象特性。在本小节中,您将了解这些特性。

基于原型的 OOP

JavaScript 实现的面向对象编程类型不同于 Java™ 代码中使用的类型。尽管 Java 编程基于一个基于类的 OOP 模型,但 JavaScript 基于一个不怎么基于类的 OOP 模型,这个模型称为基于原型的对象方向。对象只在需要的时候才被声明,而不是定义一组用于创建对象的类模板。当一个对象需要从另一个对象继承特性时,它可以简单地克隆一个原型对象的特性。原型 OOP 的一个关键优势是,对象原型可以在运行时修改,这意味着对象结构的定义不是严格的。大多数基于类的 OOP 编程语言不允许类在运行时动态改变(但有几个例外,比如 Perl、Python 和 Ruby)。

在下一小节中,您将学习如何使用原型模型在 vanilla JavaScript 中进行 OOP。

纯 JavaScript 中基本的面向对象示例

在本系列第 1 部分中,您学习了 Firefox 的 Firebug 插件,以及如何使用控制台来运行 JavaScript 代码,而不必编辑、保存和运行文件并在一个 web 页面上执行事件。打开 Firefox 并启动 Firebug。您当前所处的 web 页面无关紧要,因为这个示例不需要任何特殊 JavaScript 库。

您将创建一个充当一个对象原型的函数,而不是在 JavaScript 中声明类。然后您可以使用 new 关键字创建这个对象的一个实例。要查看这样一个示例,将清单 1 中的代码输入 Firebug 控制台:

清单 1. JavaScript 中的一个基本对象原型
function Car() { }

var myCar = new Car();
console.log(myCar);

这应该在控制台日志中生成以下输出:Object {}

实际上,您可以单击这个输出,它将把您带到 Firebug 的另一部分,您可以在那里检查一个对象的各个属性。如果您单击这个特殊对象,您将看到一条消息 “There are no properties to show for this object”。

这段代码的实际作用是定义一个对象函数Car()。然后,它使用 new 的操作符实例化一个对象,最后,它将该对象输出到 Firebug 控制台,您可以在那里进一步检查它。但是,这个对象还不够完善,因此,我们让它更有趣一些(见清单 2)。

清单 2. 定义您的对象中的一个成员变量
function Car() { }
Car.prototype.current_speed = 0;

var myCar = new Car();
console.log(myCar);

这次,控制台日志将输出更有趣的内容:Object { current_speed=0 }

单击输出将把您带到 DOM 检查器窗口,您将在该窗口中看到属性current_speed及其值0。 使用Car原型函数创建的任何对象将默认拥有一个值为 0 的current_speed属性。

如果不能加速,一辆汽车将毫无用处,因此我们向这个原型添加一个方法(见清单 3)。

清单 3. 向原型添加一个方法
function Car() { }
Car.prototype.current_speed = 0;
Car.prototype.accelerate = function(increment) {
    this.current_speed += increment;
}

var myCar = new Car();
myCar.accelerate(30);
myCar.accelerate(20);

console.log(myCar);

控制台输出应该生成以下内容:Object { current_speed=50 }

单击输出不仅将向您显示current_speed属性,还将显示该对象可用的新accelerate方法。在清单 3 中的示例中,您能看到如何调用一个对象的方法:使用圆点表示法,比如object.method(arg[0],arg[1],...,arg[N]);在本例中为myCar.accelerate(20)。这种表示法可用于访问一个对象的任何属性。将行console.log(myCar)更改为console.log(myCar.current_speed),您将看到控制台输出显示值50,而不是实际对象本身的一个表示。

下面,我们讨论如何在 JavaScript 中实现一个构造器函数。构造器是对象被实例化之后立即调用的函数。当您创建对象函数时,这翻倍(double)成为一个构造器函数。清单 4 展示了一个应用示例。

清单 4. 添加一个构造器函数体
function Car(reg_no) { 
    this.reg_no = reg_no;
    console.log('Car with registration no. '+this.reg_no+' created.');
}

Car.prototype.reg_no = '';
Car.prototype.current_speed = 0;
Car.prototype.accelerate = function(increment) {
    this.current_speed += increment;
}

var myCar = new Car('10C500');
myCar.accelerate(30);
myCar.accelerate(20);

console.log(myCar.current_speed);

在清单 4 中,Carconstructor函数接受一个参数作为其注册编号。然后,它将对象实例的注册编号设置为该参数值,并向 Firebug 控制台输出一条消息,确认实例已被创建。Firebug 控制台窗口中应该显示如清单 5 所示的输出。

清单 5. 输出
Car with registration no. undefined created.
50

为演示如何在一个对象原型上实现继承性,我们来创建一个更高级的Car原型,并添加加速、减速和换挡三个方法(见清单 6)。

清单 6. 一个更完整的Car原型
function Car(reg_no) { 
    this.reg_no = reg_no;
}

Car.prototype.reg_no = '';
Car.prototype.current_speed = 0;
Car.prototype.current_gear = 0;

Car.prototype.accelerate = function(increment) {
    this.current_speed += increment;
}
Car.prototype.decelerate = function(decrement) {
    this.current_speed -= decrement;
}
Car.prototype.increaseGear = function() {
    this.current_gear++;
}
Car.prototype.decreaseGear = function() {
    this.current_gear--;
}

现在我们以此为基础创建一个称为ATCar的继承对象原型,该原型描述一辆自动挡汽车。这个示例并不是一辆完美的汽车,因为它基于速度而不是 RPM 进行换挡,但它能够帮助您理解原型 OOP 中的继承性和多态性(见清单 7)。

清单 7. 一个ATCar对象原型,继承自Car
function ATCar(reg_no) {
    Car.call(this,reg_no);
}
ATCar.prototype = new Car();
ATCar.prototype.constructor = ATCar;

ATCar.prototype.accelerate = function(increment) {
    Car.prototype.accelerate.call(this,increment);
    if(increment >= 10) this.increaseGear();
}

ATCar.prototype.decelerate = function(decrement) {
    Car.prototype.decelerate.call(this,decrement);
    if(this.current_speed === 0) this.current_gear = 0;
    else if(decrement >= 10) this.decreaseGear();
}

您需要注意的第一件事是:构造器使用Car对象原型上的 call 函数。只要一个 ATCar 被实例化,这通常会调用父原型定义上的构造器。这让您确信您实现了继承性,这样,ATCar原型就不需要了解Car对象原型的内部工作原理。下面,您将把ATCar函数的 prototype 属性设置为Car函数的一个新实例。本质上,这将告知 JavaScript :您想在ATCar原型中继承Car原型的属性和方法。

在清单 7 中,您还覆盖了acceleratedecelerate方法,这样,在自动挡汽车上,加速和减速将导致自动换挡。在这两个方法中,您首先调用父原型的方法,这样您就不必重新实现实际加速,从而不必编写重复代码。由于这个示例只有一行代码,因此这在本例中并不那么重要,但是想象一下,如果这是一个复杂的函数,重复编写它将有多么痛苦!

最后,我们看看这个示例的应用情况(见清单 8)。

清单 8. 使用自动挡汽车原型
var myCar = new ATCar('10C500');
myCar.accelerate(30);
myCar.accelerate(20);
myCar.decelerate(5);

console.log(myCar);

这将输出已经创建的新对象。单击该对象查看细节。结果应该如图 1 所示。

图 1.myCar对象属性的 Firebug 视图

本文的主要目的是展示如何使用 Dojo 的基于类的 OOP 模拟特性,因此,上述内容只是纯 JavaScript 实现对象方向的原型途径的一个简介。要了解关于基于原型的 OOP 的更多信息,请参阅参考资料

原型 OOP 的问题

对于基于原型的面向对象编程比基于类的 OOP 好还是差,不同的人有不同的观点。根据您作为一个程序员的偏好,赞成或反对的观点都很容易驳斥。基于原型的对象方向的一个最常见的问题 — 特别是在 JavaScript 中 — 是许多开发人员对它缺乏基本了解。但是,随着对 JavaScript 有深入理解的开发人员的人数逐步上升,这个问题将不再是问题。尽管如此,许多熟悉基于类的 OOP 语言(比如 Java 语言)的程序员仍然乐于坚持使用一个基于类的系统,Dojo 提供了一些很棒的特性来模拟这种类型的系统,以便您以类似的方式编写 JavaScript 代码。下一节解释如何使用 Dojo 的这些特性在 JavaScript 中创建基于类的应用程序。

回页首

使用 Dojo 模拟基于类的 OOP

在深入讨论 Dojo 的基于类的模拟之前,重要的是要注意到,到目前为止,Dojo 仍然是一个 JavaScript 库。Java 代码和 JavaScript 不是一回事;事实上,它们的差别很大。Dojo 并不试图迫使 JavaScript 像 Java 代码那样操作,相反,它允许 Java(和其他基于类的 OOP 语言)开发人员以一种他们熟悉的方式使用 JavaScript OOP,而底层结构仍然以一种原型方式工作。

使用dojo.declare创建类

要使用 Dojo 创建类,可以使用dojo.declare函数。现在,我们使用这个函数来创建一个Car类(见清单 9)。

清单 9. 使用 dojo.declare 创建一个Car
dojo.declare("Car",null,{

});

var myCar = new Car();
console.log(myCar);

这是创建一个类并实例化该类的一个对象的基本 shell。dojo.declare函数接受 3 个参数:

  1. 类名
  2. 类继承的超类
  3. 包含该类的所有属性和方法的一个对象

在清单 9 中的示例中,您声明了一个名为 Car 的类,它不从任何超类继承,也没有任何成员变量和方法。如果您通过在 Firebug 控制台中单击输出来查看myCar对象的对象属性,您将看到类似图 2 的画面。

图 2. 从一个Dojo类创建的一个基本对象

如您所见,在 Dojo 中创建一个类将默认向从该类生成的任何对象赋予一些属性和方法。您现在的类不怎么有趣,因此,我们来添加一些属性和方法,以及一个构造器,就像上一小节中演示面向原型的方法时所做的那样(见清单 10)。

清单 10. 一个更完整的

如您所见,与 vanilla JavaScript 声明对象原型的方法相比,这种类声明方式更容易。在继续讨论继承性之前,我们先检查一下对象是否可以被实例化,以及那些方法是否有效(见清单 11)。

清单 11. 使用var myCar = new Car("10C500"); myCar.accelerate(30); myCar.accelerate(20); myCar.decelerate(5); console.log(myCar.reg_no+" travelling at "+myCar.current_speed+" mph");

这在 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 是将在 Firebug 控制台中显示的输出。

清单 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_numbermintues_remaining属性在那里,来自MediaPlayer类的disk_spacesongs在那里,来自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中被设置为2b属性已经被来自objB的值3所覆盖。而且,c属性已经被添加。这个基本示例完成后,我们来看看如何在您的多继承性示例中使用dojo.mixin

在上一个示例中创建Phone类时,您可能会回想起清单 17 中那个类的构造器中的两行。

清单 17.Phone类构造器中的行
this.phone_number = properties.phone_number;
this.minutes_remaining = properties.minutes_remaining;

由于只有两行,这还不太麻烦,但如果行比较多该怎么办呢?必须以这种方式分配属性难道不是一件很令人痛苦的事吗?而这正是dojo.mixin函数真正有用的地方!使用下面的行替换这两行(以及MediaPlayerSmartphone类中类似的行):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.providedojo.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 的面向对象特性之上,因此您在本文中所学到的知识将在下一部分中派上用场。

版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 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确实完全正常工作.但是当我点击图像时