如果您是一名只有很少或根本沒(méi)有 JavaScript 經(jīng)驗(yàn)的開(kāi)發(fā)人員,,在接觸 Dojo 時(shí)可能需要掌握一些必要的概念,。Dojo 的一個(gè)主要問(wèn)題是(在撰寫本文之際),它仍然處于其嬰兒期(版本 1.0 在 2008 年 2 月份才發(fā)布),,并且可用的文檔仍然非常有限,。本文將幫助您理解 Dojo 和 Java 代碼之間的聯(lián)系,使您在開(kāi)發(fā)應(yīng)用程序時(shí)可以快速入手并掌握這個(gè)工具箱,。
本文并沒(méi)有介紹如何獲得 Dojo 工具箱或一些必要的使用指令,,因?yàn)橐呀?jīng)有大量的資源提供了此類信息。本文主要針對(duì)從 servlet 開(kāi)發(fā)轉(zhuǎn)向 Dojo 的 Web 開(kāi)發(fā)人員,。
需要面對(duì)的主要挑戰(zhàn)之一就是理解在調(diào)用 Dojo 函數(shù)時(shí)使用的語(yǔ)法,特別是 “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 操作符可以訪問(wèn)或設(shè)置 hash 中的每個(gè)屬性(參見(jiàn)清單 2),。
清單 2. 訪問(wèn)和設(shè)置 hash 屬性
// Accessing a hash attribute... console.log(myHash.str_attr); // Setting a hash attribute... myHash.str_attr = "bar"; |
myHash
的前四個(gè)屬性的含義不言自明。事實(shí)上,,hash 可以擁有 hash 屬性,,這并不奇怪。(可以將這看作類似于原語(yǔ)和對(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)行的上下文。如果沒(méi)有指定,,函數(shù)將在定義它的閉包中運(yùn)行,。
在最簡(jiǎn)單的情況下,閉包可以被看作是使用大括號(hào)({}
)包含的任意 JavaScript 代碼。JavaScript 文件內(nèi)部聲明的函數(shù)可以使用this
訪問(wèn)在文件主體中聲明的任何變量,,但是在 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()
可以成功訪問(wèn)globalContextVariable
并顯示其值。 - 但是,,
enclosedFunction()
只可以訪問(wèn)其本地變量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 的一大功能就是能夠通過(guò)使用 dojo.connect
函數(shù)和內(nèi)置的 pub/sub 模型將對(duì)象 “連接” 起來(lái),。
類的聲明需要三個(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 代碼中,您可以通過(guò)使用各種不同的簽名聲明多個(gè)重載的構(gòu)造函數(shù),,從而支持實(shí)例化,。在一個(gè) Dojo 類中,可以聲明一個(gè) preamble
、一個(gè) constructor
和一個(gè) postscript
,,但是在大多數(shù)情況下,,您只需要聲明一個(gè)構(gòu)造函數(shù)。
- 除非混合使用了其他類來(lái)模擬多個(gè)繼承,,否則不需要用到
preamble
,,因?yàn)樗试S您在constructor
參數(shù)傳遞給擴(kuò)展類和混合類之前對(duì)其進(jìn)行處理,。 postscript
產(chǎn)生了 Dojo 小部件生命周期方法,,但對(duì)標(biāo)準(zhǔn) Dojo 類沒(méi)有什么用處,。
不一定要全部都聲明,但是要將所有值傳遞到類的實(shí)例中,,就必須將 constructor
函數(shù)聲明為 minimum,。如果 constructor
參數(shù)將被該類的其他方法訪問(wèn),必須將它們賦值給已聲明的屬性,。清單 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í)例無(wú)論在何時(shí)更新它,,修改將反映到所有其他實(shí)例中,。為了避免這個(gè)問(wèn)題,,應(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. 類屬性
超類的方法可以通過(guò)使用相同的名稱聲明屬性來(lái)擴(kuò)展,。這里和重載無(wú)關(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
來(lái)訪問(wèn) 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 代碼中訪問(wèn)類的作用域變量
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)鍵字可以訪問(wè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ù)聲明之前傳遞的(參見(jiàn)清單 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)境的開(kāi)發(fā)人員將很難適應(yīng) JavaScript,。但是 Dojo 提供了類聲明功能,使向客戶端開(kāi)發(fā)過(guò)渡變得非常簡(jiǎn)單,。充分理解上下文,,以及何時(shí)、如何設(shè)置上下文,,將為 Java 開(kāi)發(fā)人員省去很多麻煩,,并幫助他們自信地將 JavaScript 添加到自己的工具箱中。
- 您可以參閱本文在 developerWorks 全球網(wǎng)站上的 英文原文,。
- 在 DojoToolkit.org 中可以找到所有入門信息和資源,。
- 通過(guò) 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 編程示例。