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