針對 Java 開發人員的 Dojo 概念

 

Dojo 在基於Web 的應用程序中越來越受到歡迎。很多開發人員是 Java™ 編程方面的能手,但是在 JavaScript 方面卻缺乏經驗。從強類型、面向對象的編譯語言轉向動態的、弱類型腳本語言,開發人員需要經歷概念躍遷帶來的困難。這種混亂使開發人員很難正確地聲明 Dojo 類。本文將幫助梳理這種混亂,解釋爲何必須設置上下文,以及如何實現它。

簡介

如果您是一名只有很少或根本沒有 JavaScript 經驗的開發人員,在接觸 Dojo 時可能需要掌握一些必要的概念。Dojo 的一個主要問題是(在撰寫本文之際),它仍然處於其嬰兒期(版本 1.0 在 2008 年 2 月份才發佈),並且可用的文檔仍然非常有限。本文將幫助您理解 Dojo 和 Java 代碼之間的聯繫,使您在開發應用程序時可以快速入手並掌握這個工具箱。

本文並沒有介紹如何獲得 Dojo 工具箱或一些必要的使用指令,因爲已經有大量的資源提供了此類信息。本文主要針對從 servlet 開發轉向 Dojo 的 Web 開發人員。

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.hitch 將 enclosedFunction() 連接到全局上下文,這樣就可以顯示 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. 上下文連接的工作原理

聲明類

爲什麼連接如此重要?您將在聲明 Dojo 類或創建自己的部件時體驗到它的重要性。Dojo 的一大功能就是能夠通過使用 dojo.connect 函數和內置的 pub/sub 模型將對象 “連接” 起來。

類的聲明需要三個對象:

◆一個惟一的類名
◆用於擴展函數的父類(以及模擬多個繼承的 “混合” 類)
◆定義所有屬性和函數的 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 中調用超類方法的輸出

設置方法上下文

清單 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 targetArray = new ArrayList();

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 並不是在函數包圍的上下文中定義的,但是可以將上下文定義爲參數傳遞給 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 添加到自己的工具箱中。

【編輯推薦】

  1. 用JSF/DWR/DOJO創建動態Web應用
發佈了26 篇原創文章 · 獲贊 6 · 訪問量 45萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章