介紹 JavaScript 關於函數與建構者的基礎知識。理解此一基礎,將建構者內容參數化。
透過參數化的技巧,實作一個產生新類別的類別。
JavaScript 定義類別的基本方式
JavaScript 沒有類別定義的關鍵字。"定義類別"這句話在 JavaScript 中的意義,
等於是定義一個新的建構者(Constructor)。
JavaScript中的「建構者」是什麼?
答案是一個具有建立與賦予初值給個體之能力的函數實體。
A constructor is a Function object that creates and initialises objects.
簡單說,建構者就是一個函數。
參考「JavaScript的中介編程與反射能力示範 」。
以下 JavaScript 程式碼示範如何定義建構者,亦可理解為定義類別。
// 定義一個新的建構者,名稱為 Class1;
// 亦可理解為定義一個新的類別,名稱為 Class1。
function Class1 () {
}
/* public members. */
Class1 . prototype =
{
foo : function () {
print ( " foo... " );
}
}
var c1 = new Class1 (); // allocate an instance of Class1.
c1 . foo ();
配合 JavaScript 的用語習慣,在本文中主要用「建構者」一詞,而不用「類別」。
JavaScript 表達函數內文的方式
JavaScript 定義函數的語法,有兩種表達函數內文的方式。
第一種方式使用 { }
括起函數內文,這是我們熟悉的表達方式。
第二種方式則是用字串表達函數內文,詳見 ECMA-262 規格書第 15.3.2.1 節。
The last argument specifies the body (executable code) of a function; any preceding arguments specify formal
parameters.
15.3.2.1 new Function (p1, p2, ... , pn, body)
在 meta-programming 的場合中,我們通常需要採用第二種方式。
以下程式碼所表達的內容等義。
// 第1種,以 {} 括起函數內文。
function Class2 () {
this . title = "" ;
this . value = 0 ;
}
// 第2種,以字串表達函數內文。
var Class3 = new Function ( ' \
this.title=""; \
this.value = 0; ' );
建構者內容的參數化
首先,我們先複習一次常規的、一般的建構者定義內容。
class11.js 定義了一個名為 Class11 的建構者,Class11.prototype 指派其公開成員。
function Class11 () {
this . value = 0 ;
}
Class11 . prototype = {
inc : function ( v ) {
this . value += v ;
return this ;
},
val : function ( v ) {
if ( v != undefined ) { // setter
this . value = v ;
return this ;
}
else { // getter
return this . value ;
}
}
}
var o1 = new Class11 ();
print ( o1 . val ( 3 ). inc ( 5 ). val ());
接著,我們把建構者的函數內文改成字串表達,並把定義公開成員的程式碼拆開撰寫。
改成下列內容。
// 將函數內文改成字串表達。
var func_body = ' \
this.value = 0; \
' ;
// 把 prototype 成員的程式碼段落分開。
var proto = {
inc : function ( v ) {
this . value += v ;
return this ;
},
val : function ( v ) {
if ( v != undefined ) { // setter
this . value = v ;
return this ;
}
else { // getter
return this . value ;
}
}
};
// 以下的表達方式,可以將類別內容參數化。
var Class11 = new Function ( func_body );
Class11 . prototype = proto ;
var o1 = new Class11 ();
print ( o1 . val ( 3 ). inc ( 5 ). val ());
從上例的改寫程式碼中,我們可以看出建構者的函數內文與其公開成員的內容,都可以變成一個參數。
結合以上的建構者內容參數化的演示,可以寫出下列專門用來產生新的類別的建構者。
另一種說法是: 它是一個可以產生匿名類別的類別。
我個人則習慣說是「配置一個未分類個體」。
function UnclassifiedObject ( construct , proto ) {
var func_body = "" ;
if ( construct ) {
func_body = construct . toString ();
func_body = func_body . substring (
func_body . indexOf ( ' { ' ) + 1 ,
func_body . lastIndexOf ( ' } ' ));
// strip 'function() {' and '}'.
}
var c = new Function ( func_body );
// var c = construct; // ERROR! it will take a reference to construct.
if ( proto )
c . prototype = proto ;
return c ;
}
UnclassifiedObject 的用例
UnclassifiedObject 的回傳結果是一個新的類別。根據是否配合 new 運算子而有兩種使用途徑。
第一、將 UnclassifiedObject 產生的新類別指派一個名稱,使這個新類別變成一般的具名類別來用。
// 配置一個類別,並指派其名稱為 'NewClass'。
var NewClass = UnclassifiedObject (
/*construct*/
function () {
this . value = 0 ;
},
proto
);
var o1 = new NewClass ();
print ( o1 . inc ( 3 ). val ());
第二、配合 new 運算子,直接配置一個未分類個體。
// 注意運算順序!
// 先調用 UnclassifiedObject() 回傳一個新的類別,
// 此回傳值再接上 new 運算子後,就產生一個新的未分類個體。
// aka. 匿名類別實體。
var o2 = new ( UnclassifiedObject (
/*construct*/
function () {
this . value = 0 ;
},
proto
));
print ( o2 . inc ( 3 ). val ());
大部份的 JavaScript library ,例如 extJS, jQuery 等,事實上都是透過這種機制,為 JavaScript 擴充 了類別定義功能。
你或許不再需要自己設計一個類別的類別,但了解它的基礎觀念,總是百利而無害。
對於一位 JavaScript library 的設計者,這是必要知識。
相關文章
樂多舊網址: http://blog.roodo.com/rocksaying/archives/18294075.html