内容


使用 Dojo 的 Ajax 应用开发进阶教程,第 8 部分

Dijit 开发最佳实践

系列内容:

此内容是该系列 # 部分中的第 # 部分: 使用 Dojo 的 Ajax 应用开发进阶教程,第 8 部分

敬请期待该系列的后续内容。

此内容是该系列的一部分:使用 Dojo 的 Ajax 应用开发进阶教程,第 8 部分

敬请期待该系列的后续内容。

Dijit 组件(widget)是 Dojo 提供的图形用户界面组件库。它提供了 Ajax 应用开发中会用到的常用组件,可以帮助开发人员快速的构建 Ajax 应用。本文并不会介绍 Dojo 默认提供的组件,而是侧重于介绍 Dijit 组件的编程模型和最佳实践,其目的是帮助开发人员更好的开发自己的 Dijit 组件。下面首先对 Dijit 做概要介绍。

Dijit 概述

Dijit 组件的存在是 Dojo 框架区别于其它 JavaScript 框架的一个重要特性。在桌面应用开发中,开发人员大量使用图形用户界面组件库来提高开发效率。而在 Web 应用开发中,HTML 语言本身仅提供了少数基本的控件,如按钮、单选框、复选框、文本输入框和下拉列表等。而对于在 Web 应用开发中常见的一些复杂组件,如对话框、菜单、工具栏、进度条、富文本编辑器和树等,并没有提供原生的支持。在这种情况下,开发人员往往需要自己开发这样的复杂组件,这就造成了更长的开发周期和更高的开发和维护的成本。

Dojo 提供了一个种类多样的组件库。开发人员只需要简单的定制就可以在自己的应用中使用这些组件。除此之外,Dojo 还提供了完善的组件编程模型。如果默认提供的组件都不能满足需求,可以自己来开发所需的组件。遵循这个统一的编程模型,会比从头开始创建组件要容易得多。有了组件的概念之后,开发人员在设计 Web 应用的时候,就可以从比较高的抽象层次来对应用的各个部分进行划分。定义好清晰的组件接口之后,团队成员就可以各司其职,并行开发,从而提高开发效率。

在开发 Dijit 组件的时候,需要注意下面几个基本的问题。

  • 组件的粒度问题。一般来说,功能比较复杂的组件不利于复用,也不利于团队开发时的分工合作。但是过多小组件在页面上的时候,会消耗比较多的系统资源,影响性能。而性能对 Web 应用来说是一个非常重要的因素。因此需要进行一定的权衡。比较好的做法是从较大的组件开始,当发现存在代码重复的时候,再把重复的代码提取出来,重构成新的组件。这样就把划分成小组件的决策推迟到了真正需要的时候,避免过度设计。
  • 组件的接口问题。组件的接口定义了代码中的其它部分如何使用该组件。一般来说,组件可以提供三类的接口:公共属性、公共方法和事件绑定点。公共属性指的是组件提供的可以公开访问的简单数据类型属性。一般在创建组件的时候使用,用来对组件进行定制;公共方法指的是可以公开访问的 JavaScript 方法。一般在组件创建完成之后使用,用来改变组件的行为;事件绑定点是组件暴露出来的占位方法。一般由组件使用者通过 dojo.connect()来绑定到该方法上。组件使用该方法来通知使用者其内部状态的变化。这类方法一般以 on作为名称前缀。开发人员应该根据需要定义合适的接口,避免一些不好的实践。比如公共属性的值在组件创建之后,一般不推荐使用者设置其值。如果设置该属性的值是一个合理的场景的话,最好提供相应的公共方法,并以文档的形式告诉使用者正确的用法。
  • 组件之间的交互问题。组件之间如果需要相互通讯的话,最好使用组件的对象引用来完成。比如某个组件在创建另外一个组件的时候,可以把自己的对象引用作为参数传递给其创建出来的组件。后者就可以使用此对象引用来调用前者的方法。另外一种做法是通过 dojo.publish()dojo.subscribe()方法来完成。这种做法的使用比较简单,可以避免层次较深的对象引用传递。不好的地方是组件之间的关联关系不够清晰,也比较难维护。推荐的做法是优先使用第一种方式。

上面对开发 Dijit 组件的一些通用问题进行了讨论。下面开始介绍 Dijit 组件的编程模型。在编程模型的介绍过程中,会穿插介绍相关的最佳实践。首先从 Dijit 组件的核心类 dijit._Widget开始。

dijit._Widget

dijit._Widget是所有 Dijit 组件的父类。Dijit 默认提供的组件以及自己开发的组件都需要继承自此类。dijit._Widget所提供的方法涉及组件的生命周期、属性设置和获取、事件处理和其它辅助功能等。深入了解该类的这些方法的用法和实现细节,是开发自己的 Dijit 组件的基础。下面分别对 dijit._Widget提供的方法进行分类讨论。

组件生命周期

dijit._Widget提供了对组件生命周期的完整管理,包括组件的创建和销毁。Dijit 组件的生命周期管理在实现的时候,使用了模板方法(Template Method)设计模式。dijit._Widget类的 create()方法定义了 Dijit 组件创建时的生命周期的默认模板。该方法会在合适的时机调用模板中包含的其它方法。这些不同的方法构成了组件生命周期中的各个阶段。开发自己的组件的时候,可以覆写其中的某些方法,从而在感兴趣的阶段添加自己的处理逻辑。开发人员也可以覆写 create()方法来提供一套完全不同的生命周期实现。不过这种做法风险太大,不建议使用。绝大多数情况下,覆写默认模板提供的方法就足够了。dijit._Widget中定义的组件生命周期中的创建阶段如 图 1 所示。

图 1. Dijit 组件创建过程
Dijit 组件创建过程
Dijit 组件创建过程

图 1 所示,创建 Dijit 组件时的过程中包含如下几个步骤:

  1. 以声明式或是编程式的方式创建 Dijit 组件。
  2. 创建时传入的参数被混入(mixin)到当前 Dijit 组件对象中。混入完成之后,postMixInProperties()方法被调用。
  3. 如果没有为此组件对象提供 ID 的话,则自动生成一个惟一的 ID。把此对象添加到全局的组件注册表中。完成之后,buildRendering()方法被调用。
  4. 设置 Dijit 组件的属性。完成之后,postCreate()方法被调用。
  5. 当组件被添加到页面上之后,显式调用 startup()方法。
  6. 组件创建完成之后,开始正常工作。

图 1 中给出了 4 个方法的名称,其中椭圆形中的 postMixInProperties()buildRendering()postCreate()dijit._Widget提供的组件生命周期中的扩展点。自己开发的组件应该通过覆写这些方法来实现自己的逻辑。圆角矩形中的 startup()方法并不是创建过程中的一部分,需要在 Dijit 组件创建完成之后显式的调用。下面对这 4 个方法进行详细的说明。

  • postMixInProperties():在创建 Dijit 组件的时候,可以通过参数来传入一个包含各种属性的 JavaScript 对象。这些属性被混入到 Dijit 组件中,在代码中可以通过 this来引用。当混入完成之后,在创建组件的界面之前,可能还需要执行一些处理。这些处理的逻辑可以添加在 postMixInProperties()方法中。
  • buildRendering():该方法用来创建 Dijit 组件的用户界面,即 DOM 节点。该方法完成之后,Dijit 组件的 this.domNode指向的是创建完成的 DOM 节点。
  • postCreate():当 Dijit 组件的 DOM 节点创建完成之后,此方法被调用。需要注意的是,这个时候组件的 DOM 节点可能还没有被添加到当前页面文档树中。
  • startup():当 Dijit 组件及其子组件被创建完成并添加到当前页面文档树中之后,显式的调用此方法。该方法对于那些包含子组件的 Dijit 组件来说非常有用,可以用来控制子组件和进行布局。startup()方法只需要调用一次即可。调用完成之后,组件的属性 _started的值为 true,可以用来判断 startup()方法是否已经被调用过。

与创建 Dijit 组件对应的组件的销毁过程。销毁过程比较复杂,涉及到 5 个方法,如 图 2 所示。图 2 中的箭头表示的是方法之间的调用关系。

图 2. Dijit 组件销毁过程
Dijit 组件销毁过程
Dijit 组件销毁过程

图 2 所示,最常用的销毁一个 Dijit 组件的方法是 destroyRecursive()。该方法用来销毁一个 Dijit 组件及其包含的子组件。该方法会首先调用 destroyDescendants()方法来销毁子组件。由于子组件也可能包含自己的子组件,destroyDescendants()也会调用 destroyRecursive()方法来删除其子组件,从而形成一个递归的销毁过程。当子组件销毁完成之后,destroyRecursive()会调用 destroy()方法来销毁自己。destroy()方法会首先调用 uninitialize(),接着执行内部的清理工作,最后调用 destroyRendering()方法来销毁组件的 DOM 节点。需要注意的是,destroy()方法会销毁在 Dijit 组件模板中定义的子 Dijit 组件。这 5 个方法中除了 uninitialize()之外的其它 4 个方法,都有一个参数 preserveDom用来表明是否保留 Dijit 组件的 DOM 节点,不过对从模板创建出的 Dijit 组件无效。

在深入理解了 Dijit 组件的生命周期之后,就可以更好的利用 Dijit 库提供的支持来开发自己 Dijit 组件。下面介绍一些与组件生命周期相关的最佳实践。

  • 灵活覆写 Dijit 组件生命周期相关的方法来添加自己的处理逻辑。一般来说,在 postCreate()方法中添加组件相关的处理逻辑即可。如果需要添加与 DOM 节点大小和位置相关的逻辑,应该放在 startup()方法中,并要求组件的使用者显式调用。在覆写方法的时候,要通过 this.inherited(arguments);来调用 dijit._Widget类的原始方法。
  • 如果自己的 Dijit 组件在销毁的时候需要执行额外的处理,应该把相关的逻辑添加在 uninitialize()方法中,并且只包含当前组件相关的逻辑,不需要考虑子组件。destroyRecursive()方法会负责处理子组件的销毁。
  • 总是使用 destroyRecursive()来销毁一个 Dijit 组件。这样可以确保子组件总是被正常销毁,避免内存泄露。
  • 尽量不要覆写 destroyRecursive()destroyDescendants()destroy()destroyRendering()等 4 个方法。这 4 个方法封装了完整的 Dijit 组件销毁逻辑。一般来说,覆写 uninitialize()方法就已经足够了。

属性获取与设置

Dijit 组件中可能包含各种不同的属性,允许使用者对这些属性进行获取和设置。下面以一个显示电子邮件地址的 Dijit 组件来进行说明。该组件应该允许使用者获取和设置显示的电子邮件地址。一般来说,需要提供 getEmail()setEmail(email)两个方法来实现。另外,为了防止暴露邮件地址,一般需要用特殊的字符替换掉电子邮件地址中的“@”符号,如 admin@example.org被替换成 admin#example.org。这样的话就需要提供另外的两个方法。为了简化属性的获取和设置操作,dijit._Widget类中提供了 attr(name, value)方法来统一完成属性的获取和设置。当只传入一个参数的时候,如果该参数是一个字符串,则表示获取属性的值;如果参数是一个 JavaScript 对象,则用该对象中的属性和值来设置组件的属性。当传入两个参数的时候,两个参数分别表示为属性的名称和要设置的值。对于示例 Dijit 组件来说,可以通过 attr("email", "alex@example.com")来设置要显示的电子邮件地址。

对于 Dijit 组件自定义的属性,默认情况下是保持在组件对象实例中的。attr()方法直接读取和设置组件对象中对应属性的值即可。除此之外,还可以通过 attributeMap来提供从属性到 DOM 节点之间的映射。即通过改变属性的值,就可以修改 DOM 节点。如果希望在获取和设置属性的时候添加额外的处理逻辑,则需要提供满足命名规范的对应方法。该命名规范是在首字母大写的属性名称上添加特定的前缀和后缀。如对于属性 email来说,自定义的获取和设置属性的方法分别是 _getEmailAttr()_setEmailAttr()。自定义方法的优先级高于默认的方法。attr()会首先尝试在 Dijit 组件对象中查找是否存在自定义的方法。代码清单 1 中给出了一个获取和设置属性的示例。

清单 1. 属性获取和设置示例
 dojo.declare("emailDisplayer", dijit._Widget, { 
    replaceString : "", 
    _setEmailAttr : function(value) { 
        if (!value) { 
            return; 
        } 
        var originalValue = value; 
        var displayValue = value; 
        if (this.replaceString) { 
            displayValue = displayValue.replace(/@/, this.replaceString); 
        } 
        this.domNode.innerHTML = displayValue; 
        this.email = originalValue; 
    } 
 }); 

 var n = dojo.create("div", null, dojo.body()); 
 var displayer = new emailDisplayer({}, n); 
 displayer.attr("replaceChar", "#"); 
 displayer.attr("email", "alex@example.org"); 
 displayer.attr("email");

代码清单 1 所示,Dijit 组件 emailDisplayer定义了两个属性 emailreplaceString。对于属性 email提供了自定义的设置方法 _setEmailAttr()。对于属性 replaceString则使用的是默认的实现。

对于统一的 attr()方法接口和自定义的属性获取和设置方法,Dijit 组件既可以方便使用者的使用,又给了开发人员足够的灵活性。在组件开发中,尽量避免提供 Dijit 组件自己的属性获取和设置方法,而是充分利用 Dijit 组件提供的支持。

其它方法

除了上面提到的两类方法之外,dijit._Widget还提供了其它一些方法。

  • 尽量使用 connect()方法来绑定事件处理方法。其好处是在 Dijit 组件被销毁的时候,会自动调用 disconnect()来取消事件绑定。
  • 尽量使用 subscribe()方法来监听事件通知。其好处是在 Dijit 组件被销毁的时候,会自动调用 unsubscribe()来取消事件监听。

在介绍完 dijit._Widget之后,下面介绍另外一个核心类 dijit._Templated

dijit._Templated

在介绍 Dijit 组件的生命周期的时候,提到过 dijit._Widget类中的 buildRendering()方法用来创建 Dijit 组件的用户界面。如果通过 DOM 操作来创建用户界面的话,一般来说会比较复杂,维护起来也比较麻烦。dijit._Templated提供了一种从 HTML 模板中创建 Dijit 组件用户界面的方式。dijit._Templated一般是作为一个混入类的方式来使用的。它提供了自己的 buildRendering()用来从模板字符串或是文件中创建 DOM 节点。在使用 dijit._Templated的时候,有下面几点需要注意。

  • 通过 dojo.declare()方法定义新的组件的时候,dijit._Widget需要作为基类,而 dijit._Templated只能作为混入类。也就是说在父类声明中,dijit._Widget需要作为第一个出现;否则的话会出现错误。
  • 可以通过 templateStringtemplatePath两种方式来指定所使用的模板。从 Dojo 1.4 开始,建议使用 templateString来指定模板字符串。不过在 Dijit 组件开发过程中,把模板存放在单独的 HTML 文件中更加便于开发和调试。因此在开发过程中可以使用 templatePath。在发布的时候,则需要通过 Dojo 的构建过程把 HTML 文件的内容内联到组件代码中,通过 templateString来表示。
  • 通过设置属性 widgetsInTemplate的值为 true可以声明该 Dijit 组件中包含其它的组件。这些包含的组件在 destroy()方法中会被销毁。

在模板中可以使用 dojoAttachPointdojoAttachEvent两个特殊的 DOM 节点的属性。dojoAttachPoint属性的值被转换成组件对象中的一个属性的名称,该属性的值是当前的 DOM 节点。dojoAttachEvent属性的值被转换成通过 dojo.connect()完成的事件处理绑定。如 <div dojoAttachPoint="myDiv" dojoAttachEvent="onclick:show" />声明了该 DOM 节点可以在组件对象中通过 myDiv来引用,点击该节点会调用 show()方法。这两个属性的存在带来了编程上的简便。在组件对象的方法中,如果需要引用某个 DOM 节点的话,一般需要通过 DOM 查询或是 dojo.query()来完成,这样的话会比较繁琐。通过 dojoAttachPoint就避免了查询操作,使用起来更加简单。如果需要绑定事件处理的话,使用 dojoAttachEvent就免去了对 dojo.connect()方法的显式调用。不过使用这两个属性的话,会造成变量的声明和使用在不同的地方,会在一定程度上影响代码的可读性。比较好的实践如下:

  • 为了方便区分组件对象中通过 dojoAttachPoint声明的属性和一般的属性,最好为 dojoAttachPoint声明的属性名称添加统一的后缀,如 Node或是 Container。其好处是开发人员在发现带某个后缀的属性时,会明白要去模板中查找相关的声明。
  • 在引用 DOM 节点和绑定事件处理方法的时候,尽量使用统一的方式。可以统一使用 dojoAttachPointdojoAttachEvent,也可以统一使用 DOM 查询和 dojo.connect()。最好不要两种方式混用。项目开发团队应该根据团队的意见,制定出相关的代码编写规范。

在介绍完 dijit._Templated之后,下面介绍另外一个核心类 dijit._Container

dijit._Container

有些 Dijit 组件是作为其它组件的容器而存在的,如与页面布局相关的组件。作为容器的组件需要对其包含的子组件进行管理,包括查询、添加和删除子组件等。dijit._Container混入类提供了这些管理子组件的功能,自己开发的组件可以直接混入此类。dijit._Container所提供的方法如下所示:

  • addChild(widget, insertIndex):该方法用来添加一个新的子组件到给定位置上。参数 widget表示的是子组件,insertIndex表示的是添加的位置。
  • removeChild(widget):该方法用来移除一个子组件。参数 widget既可以是子组件的引用,也可以是子组件的序号。
  • getChildren():该方法返回一个包含所有子组件的数组。
  • hasChildren():该方法用来判断是否包含子组件。
  • getIndexOfChild(widget):该方法用来返回一个子组件的序号。

通过上面提到的这些方法,就可以完成对子组件的管理。在使用 dijit._Container的时候,有下面几点需要注意:

  • dijit._Container中只能包含 Dijit 组件,也就是必须继承自 dijit._Widget。不能包含普通的 DOM 节点。对于 DOM 节点,可以用一个 dijit.layout.ContentPane封装之后,再添加到 dijit._Container中。
  • dijit._Container的子组件的 DOM 节点都是属性 containerNode所表示的 DOM 节点的子节点。而 containerNode的值与 domNode不一定相同。只有 containerNode中包含的子组件才会在 destroyDescendants()方法中被销毁。一般来说,在 Dijit 组件的 domNode下指定一个 DOM 节点作为 containerNode
  • 由于 dijit._Container负责管理其中包含的子组件,其 startup()方法会负责调用子组件的 startup()方法。对于通过 addChild()方法动态添加的子组件,如果子组件的 startup()方法没有被调用过,则会调用此 startup()方法。
  • removeChild()只是将子组件移除,使其不再受 dijit._Container的管理,并不会销毁该子组件。

在介绍完 dijit._Container之后,下面介绍如何对 Dijit 组件进行管理。

Dijit 组件管理

Dojo 会负责维护当前页面上所有 Dijit 组件的一个注册表,里面包含了所有的 Dijit 组件对象。该注册表可以通过 dijit.registry来访问,它是一个 dijit.WidgetSet类的实例。dijit.WidgetSet实际上是一个 Dijit 组件的 ID 及其对象对应的查找表。通过组件的 ID 就可以查询到组件对象。dijit.WidgetSet所包含的方法用来对此查找表进行操作。这些方法包括:

  • add(widget)用来添加新组件 widgetremove(id)用来根据 ID 移除组件。
  • byId(id)用来通过 ID 查找组件;byClass(cls)用来根据类名来查找组件,如 dijit.registry.byClass("dijit.form.Button")用来查找页面上所有的 dijit.form.Button组件。该方法的返回值是一个新的 dijit.WidgetSet对象。
  • toArray()返回一个包含所有组件对象的数组。
  • forEach()filter()map()every()some():这些方法的含义与用法与 Dojo 基本库中处理数组的同名方法的含义与用法是相同的,都是用来对所包含的组件对象进行处理。

除了全局的组件注册表 dijit.registry之外,Dijit 库还提供了其它的方法。这些方法包括:

  • dijit.byId(id)用来根据 ID 在页面上查找组件。
  • dijit.getUniqueId(widgetType)用来为指定组件类别 widgetType中的新组件生成惟一的 ID。
  • dijit.findWidgets(root)用来查找指定 DOM 节点 root中所包含的 Dijit 组件。但是不包括嵌套的 Dijit 组件。
  • dijit.getEnclosingWidget(node)用来查找包含 DOM 节点 node的 Dijit 组件。

在管理 Dijit 组件的时候,有下面几个问题需要注意:

  • 当需要管理一些 Dijit 组件的时候,可以创建自己的 dijit.WidgetSet对象。这样就可以利用 dijit.WidgetSet所提供的管理功能。
  • dijit.registry是通过组件的 ID 来进行查找的,因此要求 ID 是全局惟一的。对于系统生成的 ID,是可以保证其惟一性的。不过开发人员也可以为组件提供自己的 ID,这个时候就需要格外注意 ID 的惟一性。如果试图创建一个 ID 为 myId的组件,但是页面上已经存在 ID 相同的组件,Dojo 会抛出一个异常 "Tried to register widget with id==myId but that id is already registered"。造成这种情况的原因比较多:第一种可能是由于编程失误造成了 ID 重复,这种情况下只需要修改重复的 ID 即可;另外的可能是准备复用某个 ID,但是前一个 Dijit 组件的销毁不彻底,并没有从全局组件注册表 dijit.registry中移除掉自己。当再次使用此 ID 创建组件的时候就出现了错误。在 dijit._Widget类的 destroy()方法中包含了从 dijit.registry中删除当前组件的实现。如果自己开发的组件覆写了 destroy()方法,而没有通过 this.herited(arguments)来调用父类的逻辑的话,就很容易出现这样的错误。

在介绍完 Dijit 组件管理之后,下面介绍实例化 Dijit 组件的两种方式。

实例化 Dijit 组件

一般来说,实例化 Dijit 组件有两种方式:声明式和编程式。声明式的方式指的是在 HTML 代码中以描述的方式来定义 Dijit 组件,由 Dojo 在运行时刻把这些声明的组件实例化。编程式的方式是开发人员在代码中显式的通过 new操作符来实例化某个 Dijit 组件。实例化一个 Dijit 组件需要两个参数,第一个是混入到组件实例中的包含配置属性的 JavaScript 对象,第二个则是组件所使用的 DOM 节点。这两种方式的不同之处在于如何提供这两个参数的值。下面首先介绍声明式的方式。

声明式

使用声明式的时候,需要在 DOM 节点上添加属性 dojoType,其值是 Dijit 组件类的全名。这样就声明了在运行时刻会从此 DOM 节点上创建一个新的 Dijit 组件。如 <div dojoType="dijit.form.Button" />声明了会从此 div元素上创建一个 dijit.form.Button组件。而对于混入到组件实例中的属性,则是通过此 DOM 节点上的属性值来声明的。由于在 HTML 代码中声明属性的时候只能使用字符串,而 Dijit 组件中的属性是有数据类型的,因此需要一个转换的过程。了解此过程有助解决一些属性无法设置的问题。这个转换过程具体如下:

  1. 对于通过 dojoType声明的组件类,遍历该类的 prototype对象中的属性,去掉以“_”开头的和 Object.prototype中包含的属性,其余的就是可以在声明 Dijit 组件的时候使用的属性。这些属性的值的数据类型也会被记录下来。
  2. 接着对于上一步中得到的每个属性,查看 DOM 节点是否包含有同名属性。如果有的话,则得到此属性的值,并根据数据类型进行转换。
  3. 完成类型转换之后的值被作为最终的结果。代码清单 2 中给出了一个示例。
清单 2. 声明式创建 Dijit 组件时属性类型转换示例
 dojo.declare("myWidget", dijit._Widget, { 
    count : 1, 
    valid : false, 
    names : ["Alex", "Bob"] 
 });    

 <div dojoType="myWidget" count="20" names="John, Jason" />

代码清单 2 给出的例子中,myWidget中定义了 3 个属性:countvalidnames,其数据类型分别是数字、布尔型和数组。在声明 Dijit 组件的时候,在 DOM 节点上添加了属性 countnames。属性 count的值被转换成数字,而属性 names的值被转换成数组。

除了通过 DOM 节点的属性来设置 Dijit 组件的属性之外,还可以通过 <script>子元素来声明方法。<script>元素的内容就是方法体本身。支持的声明方式有三种:

  • <script type="dojo/method" event="myHandler">:声明的 JavaScript 方法与其它简单属性一样,被混入到组件对象中。属性名称是 event的值。
  • <script type="dojo/method" >:声明的 JavaScript 方法在组件实例化之后会被立即执行。
  • <script type="dojo/connect" event="onClick" >:声明的 JavaScript 方法在组件实例化之后会通过 dojo.connect()绑定到 event所指明的事件上。

对于第一种方式,也是可以通过 DOM 节点上的属性来进行声明的。如 <div dojoType="myWidget" myHandler="alert('Hello World!');" />。不过 <script>元素提供了更多的灵活性,比如可以通过属性 args来声明参数,属性 with来使用 JavaScript 中的 with表达式。在 <script>元素内部编写方法体也更加清晰,可以使用代码缩进。代码清单 3 给出了一个使用 <script type="dojo/method" >的示例。

清单 3. <script type="dojo/method" > 使用示例
 <div dojoType="emailDisplayer" jsId="myEmailDisplayer"> 
    <script type="dojo/method" event="setEmailByName" args="name"> 
        var email = name + "@example.org"; 
        this.attr("email", email); 
    </script> 
 </div>

代码清单 3 中使用了 代码清单 1 中给出的显示电子邮件地址的 Dijit 组件,并通过 <script type="dojo/method" >定义了一个新的 JavaScript 方法 setEmailByName。通过属性 jsId可以为组件设置一个全局的引用名称。当组件实例化之后,就可以通过 myEmailDisplayer.setEmailByName("Alex")来调用方法 setEmailByName(),组件会显示出 Alex@example.org

在声明了 Dijit 组件之后,需要通过 dojo.parser.parse(rootNode, args)方法来完成组件的实例化工作。该方法可以遍历 rootNode的子孙节点,根据是否包含属性 dojoType来判断是否为 Dijit 组件。如果是的话,就实例化该组件。该方法会返回一个实例化出来的组件列表。在实例化一个 Dijit 组件的时候,默认的方式是通过 new操作符来完成的。dojo.parser.parse()方法也允许开发人员自定义组件实例化的行为。通过在组件类或是其 prototype对象上定义 markupFactory方法就可以完成。dojo.parser.parse()方法会优先检查是否存在 markupFactory方法。如果有的话,就把此方法的返回值作为实例化出来的组件对象。

编程式

与声明式的方式相比,编程式的方式相对简单。只需要通过 new操作符,并传入合适的参数即可。如 new dijit.form.Button({label : "Press Me!"})就创建了一个新的 dijit.form.Button组件。

两种方式的比较

声明式和编程式两种方式是密切相关的。只不过使用声明式的情况下,是由 Dojo 库在后台以编程的方式来实现 Dijit 组件的实例化的。两种方式从使用上来说,声明式方式比较易懂,开发人员不需要了解太多 JavaScript 语言的细节,就可以用直观的方式来定义出所需要创建的组件;编程式的方式则功能更加强大,给了开发人员更多的灵活性。如果创建组件时所需要的参数是在运行时刻动态计算出来的话,使用声明式就无法实现,只能以编程的方式来创建。

一般来说,对于简单的和容器类的组件,最好使用声明式的方式来创建。简单的组件用声明式的方式非常简洁;而容器类的组件,即混入了 dijit._Container的组件,如 dijit.layout.TabContainerdijit.layout.BorderContainer,使用声明式的方式可以很直观的定义容器的子组件,而使用编程式的方式则需要通过 addChild()来逐个添加子组件,过程比较繁琐。如果创建组件时可以允许的属性比较复杂,或是属性的值需要动态计算,则最好使用编程式的方式来创建。开发人员可以根据实际情况来选择最适合自己的组件创建方式。

总结

Dojo 默认提供的 Dijit 组件库中包含的都是一些通用的组件。在 Ajax 应用开发中,经常会需要根据业务逻辑的需要开发出自己的组件。在开发自己组件的时候,深入了解 Dijit 组件的编程模型和相关的最佳实践是大有益处的。本文介绍了 Dijit 组件编程模型中的核心类 dijit._Widgetdijit._Templateddijit._Container,以及管理和实例化 Dijit 组件相关的内容。在介绍的过程中,穿插了对最佳实践的说明,可以帮助开发人员更好的开发自己的 Dijit 组件。

声明

本人所发表的内容仅为个人观点,不代表 IBM 公司立场、战略和观点。


相关主题

static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=10
Zone=Web development
ArticleID=548005
ArticleTitle=使用 Dojo 的 Ajax 应用开发进阶教程,第 8 部分: Dijit 开发最佳实践
publish-date=09262010