针对 Java 开发人员的 Dojo 概念

声明类并设置上下文

Dojo 在基于 Web 的应用程序中越来越受到欢迎。很多开发人员是 Java™ 编程方面的能手,但是在 JavaScript 方面却缺乏经验。从强类型、面向对象的编译语言转向动态的、弱类型脚本语言,开发人员需要经历概念跃迁带来的困难。这种混乱使开发人员很难正确地声明 Dojo 类。本文将帮助梳理这种混乱,解释为何必须设置上下文,以及如何实现它。

Dave Draper, WebSphere Application Server Administrative Console 开发人员, WSO2 Inc

Dave Draper 的照片在过去六年,Dave Draper 一直担任 WebSphere Application Server Administrative Console 的开发。他是经过 Sun 认证的 Web Component 开发人员,在基于 Web 的工具开发方面具有丰富的经验。



2008 年 11 月 03 日

简介

如果您是一名只有很少或根本没有 JavaScript 经验的开发人员,在接触 Dojo 时可能需要掌握一些必要的概念。Dojo 的一个主要问题是(在撰写本文之际),它仍然处于其婴儿期(版本 1.0 在 2008 年 2 月份才发布),并且可用的文档仍然非常有限。本文将帮助您理解 Dojo 和 Java 代码之间的联系,使您在开发应用程序时可以快速入手并掌握这个工具箱。

本文并没有介绍如何获得 Dojo 工具箱或一些必要的使用指令,因为已经有大量的资源提供了此类信息。本文主要针对从 servlet 开发转向 Dojo 的 Web 开发人员。

Ajax 资源中心

请访问 Ajax 资源中心,这是有关 Ajax 编程模型信息的一站式中心,包括很多文档、教程、论坛、blog、wiki 和新闻。任何 Ajax 的新信息都能在这里找到。

JavaScript hash

需要面对的主要挑战之一就是理解在调用 Dojo 函数时使用的语法,特别是 “hash” 或 JavaScript 对象。hash 被表示为使用逗号间隔的一组属性,并且使用大括号括起。清单 1 显示了一个简单的例子,它声明了一个包含 6 个属性的 hash:一个字符串、一个整数、一个布尔值、一个未定义的属性、另一个 hash 和一个函数。

清单 1. 示例 JavaScript hash
var myHash = {
    str_attr : "foo",
    int_attr : 7,
    bool_attr : true,
    undefined_attr : null,
    hash_attr : {},
    func_attr : function() {}
};

注意,JavaScript 是弱类型的,因此尽管每个属性被初始化为一个与其名称相关的值,但仍然需要把 str_attr 属性设置为一个整数或布尔值(或其他任何类型)。使用 dot 操作符可以访问或设置 hash 中的每个属性(参见清单 2)。

清单 2. 访问和设置 hash 属性
// Accessing a hash attribute...
console.log(myHash.str_attr);

// Setting a hash attribute...
myHash.str_attr = "bar";

myHash 的前四个属性的含义不言自明。事实上,hash 可以拥有 hash 属性,这并不奇怪。(可以将这看作类似于原语和对象的 Java 类)。这是需要理解的最后一个重要属性。

函数和对象

尽管 Java 代码中有一个 java.reflection.Method 类,但它实际上只充当方法的包装器。在 JavaScript 中,该函数可以是任何可设置、引用和作为参数传递给其他函数的对象。通常,像在 Java 方法调用中声明匿名 inner 类一样,也需要在函数调用中声明新函数。

Java 方法和 JavaScript 函数之间的另一个重要区别是 JavaScript 函数可以运行在不同的上下文中。在 Java 编程中,使用 this 关键字引用所使用类的当前实例。当在 JavaScript 函数中使用时,this 引用该函数运行的上下文。如果没有指定,函数将在定义它的闭包中运行。

在最简单的情况下,闭包可以被看作是使用大括号({})包含的任意 JavaScript 代码。JavaScript 文件内部声明的函数可以使用 this 访问在文件主体中声明的任何变量,但是在 hash 内声明的函数只能使用 this 引用在 hash 内部声明的变量,除非提供其他上下文。

由于经常需要使用封闭的函数作为 Dojo 函数的参数,因此理解如何设置上下文将省去大量的调试工作。

用于指定上下文的主要 Dojo 函数是 dojo.hitch。您可能从不使用 dojo.hitch,但必须了解它是 Dojo 的关键部分,很多函数都在内部调用它。

清单 3 展示了上下文连接的工作原理(其输出显示在图 1 中):

  • 在全局上下文(globalContextVariable)中定义一个变量,在一个 hash 上下文(enclosedVariable)中声明另一个变量。
  • 函数 accessGlobalContext() 可以成功访问 globalContextVariable 并显示其值。
  • 但是,enclosedFunction() 只可以访问其本地变量 enclosedVariable(注意 globalContextVariable 的值显示为 “未定义”)。
  • 使用 dojo.hitchenclosedFunction() 连接到全局上下文,这样就可以显示 globalContextVariable(注意,enclosedVariable 现在为 “未定义”,因为它不是在运行 enclosedFunction() 的上下文中声明的)。
清单 3. 闭包和上下文
var globalContextVariable = "foo";

function accessGlobalContext() {
    // This will successfully output "foo"...
    console.log(this.globalContextVariable);
};

var myHash = {
    enclosedVariable : "bar",
    enclosedFunction : function() {
                           // Display global context variable...
                           console.log(this.globalContextVariable);

                           // Display enclosed context variable...
                           console.log(this.enclosedVariable);
                       }
};

console.log("Calling accessGlobalContext()...");
accessGlobalContext();

console.log("Calling myHash.enclosedFunction()...");
myHash.enclosedFunction();

console.log("Switch the context using dojo.hitch...");
var switchContext = dojo.hitch(this, myHash.enclosedFunction);
switchContext();
图 1. 上下文连接的工作原理
上下文连接的工作原理

声明类

声明类的技巧

  • 尽管 myClass 是一个有效的名称,但是最好使用完全限定类名的形式声明名称,例如 com.ibm.dojo.myClass。这并不表示应当把类部署到相对路径 “./com/ibm/dojo/” 下的文件系统;它只是减少了与其他类名冲突的几率。
  • 在最后一个属性之后绝不会出现 ,(逗号),因为一些浏览器将忽略它(FireFox),但是其他浏览器(Internet Explorer)会将它弹出。这条规则也适用于 hash 对象的声明。

为什么连接如此重要?您将在声明 Dojo 类或创建自己的部件时体验到它的重要性。Dojo 的一大功能就是能够通过使用 dojo.connect 函数和内置的 pub/sub 模型将对象 “连接” 起来。

类的声明需要三个对象:

  1. 一个惟一的类名
  2. 用于扩展函数的父类(以及模拟多个继承的 “混合” 类)
  3. 定义所有属性和函数的 hash

清单 4 展示了最简单的类声明方式,清单 5 展示了该类的实例化。

清单 4. 基本的类声明
dojo.declare(
   "myClass",
   null,
   {}
);
清单 5. 基本的类实例化
var myClassInstance = new myClass();

如果希望声明一个 “真正的”(即有用的)Dojo 类,那么一定要理解构造函数。在 Java 代码中,您可以通过使用各种不同的签名声明多个重载的构造函数,从而支持实例化。在一个 Dojo 类中,可以声明一个 preamble、一个 constructor 和一个 postscript,但是在大多数情况下,您只需要声明一个构造函数。

  • 除非混合使用了其他类来模拟多个继承,否则不需要用到 preamble,因为它允许您在 constructor 参数传递给扩展类和混合类之前对其进行处理。
  • postscript 产生了 Dojo 小部件生命周期方法,但对标准 Dojo 类没有什么用处。

不一定要全部都声明,但是要将所有值传递到类的实例中,就必须将 constructor 函数声明为 minimum。如果 constructor 参数将被该类的其他方法访问,必须将它们赋值给已声明的属性。清单 6 展示了一个类,它只将其中一个 constructor 参数赋值给一个类属性,并尝试在另一个方法中引用它们。

清单 6. 赋值构造函数参数
dojo.declare(
    "myClass",
    null,
    {
        arg1 : "",
        constructor : function(arg1, arg2) {
                          this.arg1 = arg1;
                      },
        myMethod : function() {
                       console.log(this.arg1 + "," + this.arg2);
                   }
    }
);

var myClassInstance = new myClass("foo", "bar");
myClassInstance.myMethod();
图 2. 赋值构造函数参数的结果
赋值构造函数参数的结果

复杂的属性规则

类属性可以在声明时进行初始化,但是如果使用复杂对象类型(例如 hash 或数组)初始化属性,该属性将类似于 Java 类中的公共静态变量。这意味着任何实例无论在何时更新它,修改将反映到所有其他实例中。为了避免这个问题,应当在构造函数中初始化复杂属性;然而,对于字符串、布尔值等简单属性则不需要这样做。

清单 7. 全局类属性
dojo.declare(
    "myClass",
    null,
    {
        globalComplexArg : { val : "foo" },
        localComplexArg : null,
        constructor : function() {
                          this.localComplexArg = { val:"bar" };                          
                      }
    }
);

// Create instances of myClass A and B...
var A = new myClass();
var B = new myClass();

// Output A's attributes...
console.log("A's global val: " + A.globalComplexArg.val); 
console.log("A's local val: " + A.localComplexArg.val); 

// Update both of A's attributes...
A.globalComplexArg.val = "updatedFoo";
A.localComplexArg.val = "updatedBar";

// Update B's attributes...
console.log("A's global val: " + B.globalComplexArg.val);
console.log("A's local val: " + B.localComplexArg.val);
图 3. 类属性
类属性

覆盖方法

超类的方法可以通过使用相同的名称声明属性来扩展。这里和重载无关,因为 JavaScript 将忽略任何意外的参数并使用 null 代替任何缺失的参数。在 Java 代码中,如果要调用被覆盖的方法,就必须在超类上调用该方法(即 super().methodName(arg1, arg1);),但在 Dojo 中,将使用 inherited 方法(this.inherited(arguments);)。清单 8 展示了两个已声明的类,其中 child 扩展了 parent,覆盖了它的 helloWorld 方法,但是调用 inherited 来访问 parent 的函数。

清单 8. 在 Dojo 中调用超类方法
dojo.declare(
    "parent",
    null,
    {
        helloWorld : function() {
                         console.log("parent says 'hello world'");
                     }
    }
);

dojo.declare(
    "child",
    parent,
    {
        helloWorld : function() {
                         this.inherited(arguments); // Call superclass method...
                         console.log("child says 'hello world'");
                     }
    }
);

var child = new child();
child.helloWorld();
图 4. 在 Dojo 中调用超类方法的输出
在 Dojo 中调用超类方法的输出

设置方法上下文

清单 9 展示了一个实例化后的 Java 类,它将字符串数组中的元素复制到一个字符串 ArrayList。显然,使用清单 10 的代码可以在 Dojo 中提供相同的功能(注意,在构造函数中实例化 targetArray,防止它变成全局性的)。不幸的是,它将导致图 5 所示的错误消息,因为在 dojo.forEach 方法调用中声明的函数创建了一个闭包,该闭包将 this 定义为引用它本身。

清单 9. 在 Java 代码中访问类的作用域变量
import java.util.ArrayList;

public class MyClass
{
    // Declare an ArrayList of Strings...
    private ArrayList<String> targetArray = new ArrayList<String>();

    public MyClass(String[] sourceArray)
    {
        // Copy each element of a String[] into the ArrayList...
        for (String val: sourceArray) 
        {
            this.targetArray.add(val);
        }
    }
}
清单 10. 在 Dojo 中缺失上下文
dojo.declare(
    "myClass",
    null,
    {
        targetArray: null,
        constructor: function(source) {
                         // Initialise in constructor to avoid making global
                         this.targetArray = []; 

                         // Copy each element from source into target...
                         dojo.forEach(source, 
                                    function(item) {
                                        this.targetArray[this.targetArray.length] = item;
                                    });
                     },
    }
);

// This will cause an error!
var myClass = new myClass(["item1","item2"]);
图 5. 在 Dojo 中缺失上下文的输出
在 Dojo 中缺失上下文的输出

尽管 targetArray 并不是在函数包围的上下文中定义的,但是可以将上下文定义为参数传递给 Dojo 函数。这意味着 this 关键字可以访问在该上下文中声明的任何对象(包括函数)。清单 11 显示了正确的实现(注意,增加的代码用粗体表示)。

清单 11. 在 Dojo 中设置正确的上下文
dojo.declare(
    "myClass",
    null,
    {
        targetArray: null,
        constructor: function(source) {
                         // Initialise in constructor to avoid making global
                         this.targetArray = []; 

                         // Copy each element from source into target...
                         dojo.forEach(source, 
                                    function(item) {
                                        this.targetArray[this.targetArray.length] = item;
                                    }, this);
                     },
    }
);

上下文并不总是作为 Dojo 函数签名中的相同参数传递的:

  • dojo.subscribe 中,上下文是在函数声明之前传递的(参见清单 12)。
  • dojo.connect 中,应该分别提供定义 trigger 方法和 target 方法的上下文。清单 13 展示了一个例子,其中 obj1 定义 methodA 的上下文,而 obj2 定义 methodB 的上下文。对 obj1 调用 methodA 将导致对 obj2 调用 methodB
清单 12. 在 dojo.subscribe 中设置上下文
dojo.declare(
    "myClass",
    null,
    {
        subscribe : function() {
                        dojo.subscribe("publication",
                                       this, 
                                       function(pub) { 
                                           this.handlePublication(pub);
                                       });
                    },

        handlePublication : function(pub) {
                                console.log("Received: " + pub);
                            }
    }
);
清单 13. 在 dojo.connect 中设置上下文
dojo.connect(obj1, "methodA", obj2, "methodB");

结束语

已习惯构化 Java 代码环境的开发人员将很难适应 JavaScript。但是 Dojo 提供了类声明功能,使向客户端开发过渡变得非常简单。充分理解上下文,以及何时、如何设置上下文,将为 Java 开发人员省去很多麻烦,并帮助他们自信地将 JavaScript 添加到自己的工具箱中。

参考资料

  • 您可以参阅本文在 developerWorks 全球网站上的 英文原文
  • DojoToolkit.org 中可以找到所有入门信息和资源。
  • 通过 developerWorks 文章 JavaScript Development Toolkit 简介(developerWorks,2008 年 5 月)了解更多基于 Eclipse 工具的信息,这有助于编写 JavaScript。
  • 浏览 技术书店,查找有关本文所述主题和其他技术主题的图书。
  • 在 developerWorks Ajax 资源中心 找到更多有关其他 Ajax 技术(包括 Dojo)的信息。
  • 还可以获得有关 Dojo API 的全部参考资料。
  • Dojo 专区 获得一些优秀的 Dojo 编程示例。

条评论

developerWorks: 登录

标有星(*)号的字段是必填字段。


需要一个 IBM ID?
忘记 IBM ID?


忘记密码?
更改您的密码

单击提交则表示您同意developerWorks 的条款和条件。 查看条款和条件

 


在您首次登录 developerWorks 时,会为您创建一份个人概要。您的个人概要中的信息(您的姓名、国家/地区,以及公司名称)是公开显示的,而且会随着您发布的任何内容一起显示,除非您选择隐藏您的公司名称。您可以随时更新您的 IBM 帐户。

所有提交的信息确保安全。

选择您的昵称



当您初次登录到 developerWorks 时,将会为您创建一份概要信息,您需要指定一个昵称。您的昵称将和您在 developerWorks 发布的内容显示在一起。

昵称长度在 3 至 31 个字符之间。 您的昵称在 developerWorks 社区中必须是唯一的,并且出于隐私保护的原因,不能是您的电子邮件地址。

标有星(*)号的字段是必填字段。

(昵称长度在 3 至 31 个字符之间)

单击提交则表示您同意developerWorks 的条款和条件。 查看条款和条件.

 


所有提交的信息确保安全。


static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=10
Zone=Web development, Java technology
ArticleID=349532
ArticleTitle=针对 Java 开发人员的 Dojo 概念
publish-date=11032008