如果您是一名只有很少或根本沒有 JavaScript 經(jīng)驗(yàn)的開發(fā)人員,在接觸 Dojo 時(shí)可能需要掌握一些必要的概念。Dojo 的一個(gè)主要問題是(在撰寫本文之際),它仍然處于其嬰兒期(版本 1.0 在 2008 年 2 月份才發(fā)布),并且可用的文檔仍然非常有限。本文將幫助您理解 Dojo 和 Java 代碼之間的聯(lián)系,使您在開發(fā)應(yīng)用程序時(shí)可以快速入手并掌握這個(gè)工具箱。
本文并沒有介紹如何獲得 Dojo 工具箱或一些必要的使用指令,因?yàn)橐呀?jīng)有大量的資源提供了此類信息。本文主要針對(duì)從 servlet 開發(fā)轉(zhuǎn)向 Dojo 的 Web 開發(fā)人員。
需要面對(duì)的主要挑戰(zhàn)之一就是理解在調(diào)用 Dojo 函數(shù)時(shí)使用的語法,特別是 “hash” 或 JavaScript 對(duì)象。hash 被表示為使用逗號(hào)間隔的一組屬性,并且使用大括號(hào)括起。清單 1 顯示了一個(gè)簡(jiǎn)單的例子,它聲明了一個(gè)包含 6 個(gè)屬性的 hash:一個(gè)字符串、一個(gè)整數(shù)、一個(gè)布爾值、一個(gè)未定義的屬性、另一個(gè) hash 和一個(gè)函數(shù)。
清單 1. 示例 JavaScript hash
var myHash = {
str_attr : "foo",
int_attr : 7,
bool_attr : true,
undefined_attr : null,
hash_attr : {},
func_attr : function() {}
};
|
注意,JavaScript 是弱類型的,因此盡管每個(gè)屬性被初始化為一個(gè)與其名稱相關(guān)的值,但仍然需要把 str_attr 屬性設(shè)置為一個(gè)整數(shù)或布爾值(或其他任何類型)。使用 dot 操作符可以訪問或設(shè)置 hash 中的每個(gè)屬性(參見清單 2)。
清單 2. 訪問和設(shè)置 hash 屬性
// Accessing a hash attribute...
console.log(myHash.str_attr);
// Setting a hash attribute...
myHash.str_attr = "bar";
|
myHash 的前四個(gè)屬性的含義不言自明。事實(shí)上,hash 可以擁有 hash 屬性,這并不奇怪。(可以將這看作類似于原語和對(duì)象的 Java 類)。這是需要理解的最后一個(gè)重要屬性。
盡管 Java 代碼中有一個(gè) java.reflection.Method 類,但它實(shí)際上只充當(dāng)方法的包裝器。在 JavaScript 中,該函數(shù)可以是任何可設(shè)置、引用和作為參數(shù)傳遞給其他函數(shù)的對(duì)象。通常,像在 Java 方法調(diào)用中聲明匿名 inner 類一樣,也需要在函數(shù)調(diào)用中聲明新函數(shù)。
Java 方法和 JavaScript 函數(shù)之間的另一個(gè)重要區(qū)別是 JavaScript 函數(shù)可以運(yùn)行在不同的上下文中。在 Java 編程中,使用 this 關(guān)鍵字引用所使用類的當(dāng)前實(shí)例。當(dāng)在 JavaScript 函數(shù)中使用時(shí),this 引用該函數(shù)運(yùn)行的上下文。如果沒有指定,函數(shù)將在定義它的閉包中運(yùn)行。
在最簡(jiǎn)單的情況下,閉包可以被看作是使用大括號(hào)({})包含的任意 JavaScript 代碼。JavaScript 文件內(nèi)部聲明的函數(shù)可以使用this 訪問在文件主體中聲明的任何變量,但是在 hash 內(nèi)聲明的函數(shù)只能使用 this 引用在 hash 內(nèi)部聲明的變量,除非提供其他上下文。
由于經(jīng)常需要使用封閉的函數(shù)作為 Dojo 函數(shù)的參數(shù),因此理解如何設(shè)置上下文將省去大量的調(diào)試工作。
用于指定上下文的主要 Dojo 函數(shù)是 dojo.hitch。您可能從不使用 dojo.hitch,但必須了解它是 Dojo 的關(guān)鍵部分,很多函數(shù)都在內(nèi)部調(diào)用它。
清單 3 展示了上下文連接的工作原理(其輸出顯示在圖 1 中):
- 在全局上下文(
globalContextVariable)中定義一個(gè)變量,在一個(gè) hash 上下文(enclosedVariable)中聲明另一個(gè)變量。 - 函數(shù)
accessGlobalContext()可以成功訪問globalContextVariable并顯示其值。 - 但是,
enclosedFunction()只可以訪問其本地變量enclosedVariable(注意globalContextVariable的值顯示為 “未定義”)。 - 使用
dojo.hitch將enclosedFunction()連接到全局上下文,這樣就可以顯示globalContextVariable(注意,enclosedVariable現(xiàn)在為 “未定義”,因?yàn)樗皇窃谶\(yùn)行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. 上下文連接的工作原理
為什么連接如此重要?您將在聲明 Dojo 類或創(chuàng)建自己的部件時(shí)體驗(yàn)到它的重要性。Dojo 的一大功能就是能夠通過使用 dojo.connect 函數(shù)和內(nèi)置的 pub/sub 模型將對(duì)象 “連接” 起來。
類的聲明需要三個(gè)對(duì)象:
- 一個(gè)惟一的類名
- 用于擴(kuò)展函數(shù)的父類(以及模擬多個(gè)繼承的 “混合” 類)
- 定義所有屬性和函數(shù)的 hash
清單 4 展示了最簡(jiǎn)單的類聲明方式,清單 5 展示了該類的實(shí)例化。
清單 4. 基本的類聲明
dojo.declare(
"myClass",
null,
{}
);
|
清單 5. 基本的類實(shí)例化
var myClassInstance = new myClass(); |
如果希望聲明一個(gè) “真正的”(即有用的)Dojo 類,那么一定要理解構(gòu)造函數(shù)。在 Java 代碼中,您可以通過使用各種不同的簽名聲明多個(gè)重載的構(gòu)造函數(shù),從而支持實(shí)例化。在一個(gè) Dojo 類中,可以聲明一個(gè) preamble、一個(gè) constructor 和一個(gè) postscript,但是在大多數(shù)情況下,您只需要聲明一個(gè)構(gòu)造函數(shù)。
- 除非混合使用了其他類來模擬多個(gè)繼承,否則不需要用到
preamble,因?yàn)樗试S您在constructor參數(shù)傳遞給擴(kuò)展類和混合類之前對(duì)其進(jìn)行處理。 postscript產(chǎn)生了 Dojo 小部件生命周期方法,但對(duì)標(biāo)準(zhǔn) Dojo 類沒有什么用處。
不一定要全部都聲明,但是要將所有值傳遞到類的實(shí)例中,就必須將 constructor 函數(shù)聲明為 minimum。如果 constructor 參數(shù)將被該類的其他方法訪問,必須將它們賦值給已聲明的屬性。清單 6 展示了一個(gè)類,它只將其中一個(gè) constructor 參數(shù)賦值給一個(gè)類屬性,并嘗試在另一個(gè)方法中引用它們。
清單 6. 賦值構(gòu)造函數(shù)參數(shù)
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. 賦值構(gòu)造函數(shù)參數(shù)的結(jié)果
類屬性可以在聲明時(shí)進(jìn)行初始化,但是如果使用復(fù)雜對(duì)象類型(例如 hash 或數(shù)組)初始化屬性,該屬性將類似于 Java 類中的公共靜態(tài)變量。這意味著任何實(shí)例無論在何時(shí)更新它,修改將反映到所有其他實(shí)例中。為了避免這個(gè)問題,應(yīng)當(dāng)在構(gòu)造函數(shù)中初始化復(fù)雜屬性;然而,對(duì)于字符串、布爾值等簡(jiǎn)單屬性則不需要這樣做。
清單 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. 類屬性
超類的方法可以通過使用相同的名稱聲明屬性來擴(kuò)展。這里和重載無關(guān),因?yàn)?JavaScript 將忽略任何意外的參數(shù)并使用 null 代替任何缺失的參數(shù)。在 Java 代碼中,如果要調(diào)用被覆蓋的方法,就必須在超類上調(diào)用該方法(即 super().methodName(arg1, arg1);),但在 Dojo 中,將使用 inherited 方法(this.inherited(arguments);)。清單 8 展示了兩個(gè)已聲明的類,其中child 擴(kuò)展了 parent,覆蓋了它的 helloWorld 方法,但是調(diào)用 inherited 來訪問 parent 的函數(shù)。
清單 8. 在 Dojo 中調(diào)用超類方法
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 中調(diào)用超類方法的輸出
清單 9 展示了一個(gè)實(shí)例化后的 Java 類,它將字符串?dāng)?shù)組中的元素復(fù)制到一個(gè)字符串 ArrayList。顯然,使用清單 10 的代碼可以在 Dojo 中提供相同的功能(注意,在構(gòu)造函數(shù)中實(shí)例化 targetArray,防止它變成全局性的)。不幸的是,它將導(dǎo)致圖 5 所示的錯(cuò)誤消息,因?yàn)樵?nbsp;dojo.forEach 方法調(diào)用中聲明的函數(shù)創(chuàng)建了一個(gè)閉包,該閉包將 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 中缺失上下文的輸出
盡管 targetArray 并不是在函數(shù)包圍的上下文中定義的,但是可以將上下文定義為參數(shù)傳遞給 Dojo 函數(shù)。這意味著 this 關(guān)鍵字可以訪問在該上下文中聲明的任何對(duì)象(包括函數(shù))。清單 11 顯示了正確的實(shí)現(xiàn)(注意,增加的代碼用粗體表示)。
清單 11. 在 Dojo 中設(shè)置正確的上下文
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 函數(shù)簽名中的相同參數(shù)傳遞的:
- 在
dojo.subscribe中,上下文是在函數(shù)聲明之前傳遞的(參見清單 12)。 - 在
dojo.connect中,應(yīng)該分別提供定義 trigger 方法和 target 方法的上下文。清單 13 展示了一個(gè)例子,其中obj1定義methodA的上下文,而obj2定義methodB的上下文。對(duì)obj1調(diào)用methodA將導(dǎo)致對(duì)obj2調(diào)用methodB。
清單 12. 在 dojo.subscribe 中設(shè)置上下文
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 中設(shè)置上下文
dojo.connect(obj1, "methodA", obj2, "methodB"); |
已習(xí)慣構(gòu)化 Java 代碼環(huán)境的開發(fā)人員將很難適應(yīng) JavaScript。但是 Dojo 提供了類聲明功能,使向客戶端開發(fā)過渡變得非常簡(jiǎn)單。充分理解上下文,以及何時(shí)、如何設(shè)置上下文,將為 Java 開發(fā)人員省去很多麻煩,并幫助他們自信地將 JavaScript 添加到自己的工具箱中。
- 您可以參閱本文在 developerWorks 全球網(wǎng)站上的 英文原文。
- 在 DojoToolkit.org 中可以找到所有入門信息和資源。
- 通過 developerWorks 文章 JavaScript Development Toolkit 簡(jiǎn)介(developerWorks,2008 年 5 月)了解更多基于 Eclipse 工具的信息,這有助于編寫 JavaScript。
- 瀏覽 技術(shù)書店,查找有關(guān)本文所述主題和其他技術(shù)主題的圖書。
- 在 developerWorks Ajax 資源中心 找到更多有關(guān)其他 Ajax 技術(shù)(包括 Dojo)的信息。
- 還可以獲得有關(guān) Dojo API 的全部參考資料。
- 從 Dojo 專區(qū) 獲得一些優(yōu)秀的 Dojo 編程示例。






