JavaScript的中介編程與反射能力示範
基於某些原因,我這幾天嘗試分別以 JavaScript, PHP, Ruby (排名按字母順序) 實現同一個簡單的功能,這個功能用了簡單的反射與中介編程技巧。 主要目的是看這些語言在動態型別、中介編程、迭代與反射語法方面的表現。 最後,我會用 Java 語言來實現這個需求,「展現Java語言的特點」。
這篇構想中的文章,愈寫愈長。我想了想,還是按程式語言拆成幾篇,先把要示範的源碼與說明發佈上來。 這是第一篇發佈的,內容是 JavaScript 的實作,示範了兩個基本的中介編程技巧: foreach 和 accessor。
建構者(Constructor)
所有建構者都是個體,但並非每個個體都是建構者。
ECMAScrpit Language Specification, page 3
All constructors are objects, but not all objects are constructors.
所以 Abc = {};
就只是配置一個單純的個體,而不會產生一個建構者。
那麼建構者從何而來?
建構者是一個具有建立與賦予初值給個體之能力的函數實體。
ECMAScript Language Specification, page 4
A constructor is a Function object that creates and initialises objects.
和 Java 的半調子 OOP 世界不同,在 JavaScrpit 的世界中,函數本身就是一個個體。 當我們定義一個函數,並且函數內部執行了對自己增加成員並賦予成員初值的動作時, 這個函數實體就被視為一個建構者。用 Java 說法,這個東西是一個類別。
在 JavaScript/ECMAScript 裡,配置建構者的寫法有兩種。 如下所示:
1.配置一個名稱叫 F 的建構者。
2.配置一個新函數,指派給變量 F。
這兩種寫法等義。
Prototype 與可繼承方法
ECMAScript 規範的 Prototype 繼承機制有些詭秘。
我們定義在建構者內的成員(寫在 new Function(){...}
中的變數與函數等),屬於建構者私有的 Prototype ,只有直接透過此建構者配置出來的個體可以用。
如果我們想要定義可以被衍生的建構者繼承的內容,我們要添加到基礎建構者的公開 Prototype 。
在此例中(完整源碼在文末),我們先定義了名叫 Class 的建構者,再從此衍生一個名叫 Data 的建構者。 接著我們想要在 Class 上定義 foreach 與 accssor 的可繼承方法,讓衍生的 Data 建構者可以使用,那我們就要把這兩個函數添加到 Class.prototype 此一公開的 Prototype 。 故而形成了先在第5行配置建構者,再在第18行與第32行添加方法的寫法。
如果對 JavaScript 的成員封裝機制有興趣,請繼續看我先前寫的 掌握 JavaScript 的「封裝」特性, part 1 。
中介編程技巧示範
接下來要示範兩種最常見的中介編程技巧,我們要加上 foreach 和預設存取器 accessor 功能。
第一個中介編程技巧 foreach
請看源碼第18到22行。如你所見,非常簡單的演算法。透過函數個體與匿名函數,我自定了一個 foreach 語法。 用匿名函數定義你的迭代器(iterator),傳給 foreach 即可。第83-85行是 foreach 的使用範例。 不過我沒有把這個 foreach 能力塞到最基底的 Object 類別中,所以 foreach 只作用於 Class 的衍生類別中。
這是個非常基本的技巧,但是用途很廣。 流行的 JavaScript 框架,如 Prototype, jQuery, Dojo, Yahoo UI 都實作了這項能力。 當然他們實作的 foreach 功能都比我這個示範用的強 :P
如果對 JavaScript 的匿名函數有興趣,請繼續看我先前寫的 The practice of anonymous recursion function in JavaScript 。
第二個中介編程技巧 accessor
JavaScript 不支援 PHP magic method 或類似的功能(參閱活用 PHP5 的 magic methods),也不支援 Ruby 的 accessor 或是 C# 3.0 的自動屬性完成語法,所以要用一些技巧實作。
調用 Function 的建構者時,會將最後一個參數轉為字串,視之為新函數的函數碼。
ECMAScript Language Specification, page 37
The last parameter provided to the Function constructor is converted to a string and treated as the FunctionBody.
利用這個規範,設計我的 accessor 語法。請看源碼第32到51行。
我自定的 accessor 語法是如下,接受一個 hash table 指示要套用預設存取器的屬性清單。
例如: accessor( {name1:1, name2:"abc"} )
。
至於 getter 和 setter 的寫法,則是按照動態語言常用的設計慣例,只用一個函數實現,這個函數只接受一個參數。
若調用屬性存取函數時無參數,則視為 getter 回傳屬性值。若給予一個參數,則視為 setter ,將屬性之值指派為參數值。
例如針對 content 屬性,就定義一個叫 content(value)
的存取函數(在本文中是中介編程自動定義)。
其運作內容如下:
本文透過中介編程自動產生了指定屬性的預設存取器函數,故而在源碼中看不到如上例般明顯示存取函數定義。 第108-110行則是示範利用中介編程產生的 setter 與 getter 存取 content 屬性。
data.js, 本文範例用源碼
JavaScript 使用反射(reflection)的場合無所不在,在本文示範源碼中就一直在用。 但是不熟悉 JavaScript 的讀者可能會困惑「到底反射的程式碼寫在呢?」 有此困惑的讀者請先看 什麼是 reflection? , 看完 JavaScript 的反射語法後,再回來看本文就知道反射的程式碼寫在哪了。 在強型態的動態語言中,一個個體認識自己(自識)是再自然不過的事,所謂反射就像呼吸一樣自然,讓人感覺不到它的存在。
相關文章
- 再探 JavaScript的中介編程 foreach
- PHP的中介編程與反射能力示範
- JavaScript的類別定義擴充能力
- Ruby的中介編程與反射能力示範
- 從中介編程與反射能力來談 Java 語言
- 產生新類別的類別
樂多舊回應