前端要給力之:原子,與原子聯結的友類、友函數

作者: aimingoo  發布時間: 2010-12-29 16:41  閱讀: 776 次  推薦: 0   原文鏈接   [收藏]  

JavaScript中的原子(Atom)是QoBean中提出的一個重要概念,借鑒自erlang,但具有與后者不同的含義。在QoBean 里,Meta(元)與Atom(原子)是一對概念,前者表明執行系統中的最小單位,后者表明數據系統中的最小單位。QoBean約定這兩個東西為一切元編程的初始,即最小化的執行系統與數據系統模型。

有什么意義呢?沒什么意義。這只具備理論上的完整性。為了描述這種完整性,QoBean寫了兩個相當無厘頭的函數:

 
// Atom system
// - atom object for data
function Atom(atom) {
return atom {};
}

// Meta system
// - meta functional for code
function Meta(func, baseMeta) {
func.meta
= baseMeta arguments.callee;
return func;
}

// meta is meta for self.
// Meta = Meta(Meta);
Meta(Meta);

好了。接下來的一切故事,從Atom開始,至于Meta(),我們今后再講。

一、原子

Atom()函數只有一行代碼,即:

 
return atom {}

atom是傳入的參數。如果有該參數,則Atom()認為它是一個原子,返回之;如果沒有,則創建一個空白對象作為原子,返回。

Atom()并沒有檢查atom參數的有效性,但在這里QoBean強制約定“atom參數必須是一個對象實例”。之所以使用強制約定,而不是參數類型檢查,這與QoBean在元語言上的基本思想有關:僅從元語言角度,QoBean認為JavaScript只有對象和函數兩種類型,且函數也是一種對象。所以,對于Atom()來說,以下三種情況是合法的:

 
// 函數(包括構造器)可以作為atom
a1 = Atom(function() {});
// 對象實例可以作為atom
a2 = Atom(new Object());
// 也可以直接獲取一個atom對象
a3 = Atom();

當然有人會問:憑什么說一個函數從作為atom參數傳入Atom(),再原封不動的傳出來,就成了一個“原子”了呢?答案是:JavaScript 固有的特性,任何兩個對象既不相等(==),也不全等(===)。

也就是說,以JavaScript的固有性質來說,任何一個對象實例其實就是一個原子。即使任意兩個空白的對象直接量,也是互不相等的。即:

 
alert( {} == {} ); // 顯示false

當然,由于任何函數其實都是Function()的實例,所以也具有相同的性質:

 
// 顯示 true, 函數作為對象,是Function()的實例
function foo() {};
alert(foo
instanceof Function);
// 顯示false, 函數都以原子對象的形式存在,故而互不相等
alert((function(){}) == (function(){}));

二、原子的應用(1):識別器

在《JavaScript語言精髓與編程實踐》中,談到過對象的原子性的一種作用,亦即是作為“識別器”。例如說,我們知道硬幣有正反兩面,所以我們可以寫這樣的一個對象:

 
x = {
front:
true,
back:
true
}

顯然我們可以用('front' in x)或者(x['front'])來識別它。但是這就存在了一個問題,因為x對象本來就有toString這樣的屬性,所以是不是說 ('toString' in x)為true,因此就表明x有一個名為'toString'的面了呢?同理,如果有人為x添加了一'middle'屬性,那么將無法檢查是x原本就有 middle這個面呢,還是被某些代碼污染了?

顯然,按照對象設計的原理來說,將”屬性”暴露出來,并可以任意讀寫,是導致這一切的根源。解決它的法子也很簡單:用特定的方法來讀取之。例如我們要查詢"有沒有某個面":

 
x = {
front:
true,
back:
true,
query:
function(side) { reutnr side in this }
}

我們跨出了正確的一步。但是,與此前完全相同的原因,我們調用x.query('toString')時仍然返回true。這顯然不是我們想要的,因為硬幣顯然沒有'toString'這樣的'面'。

好吧,我們再向前行一步。我們知道任何一個對象都具有原子性,也就是說唯一。我們只要創建一個對象,讓他成為query所需要的一個“鑰匙”;然后把這個鑰匙藏起來,這樣誰也找不到它,于是誰也不能干擾這個對象所約束的那些性質了。

實現起來也很簡單:

 
var coin = function() {
var exist = Atom();
var sides = {
front: exist,
back: exist
}

return {
query:
function(side) { return sides[side] === exist }
}
}();

好了,就這樣。現在如果你調用coin.query('front')就一定返回true,而qoin.query('toString')則返回 false了。

不過馬上就會有人跳起來了:你這不是多此一舉嗎?既然sides已經是私有的了,就不必擔心外面隨意添加成員了呵!再則,不使用exist而使用類似true之類的值不也能判斷嗎?

是的,初看起來上述的置疑都對。不過在復雜的系統環境中,會存在三個問題,一個是sides[side]的效率不錯,總比用數組實現來得強;第二個是,如果sides不在當前的函數內呢?第三個問題則更麻煩,如果你使用true之類的值,又如何避免第三方的代碼通過Object.prototype 來添加一個成員呢?

例如說,假定query()使用"sides[side] === true"來檢測,的確可以避免toString之類的影響,但是如果有人寫一行代碼"Object.prototype.xxx = true",那coin.query('xxx')就將讓人傻眼了。

所以,我們最好是找把鑰匙藏起來,藏得好好的,別人都看不見。

三、原子的應用(2):友類與友函數

JavaScript沒有明確的“類”的概念,所以這里討論的友類與友函數其實是同一回事,只是Atom()作用于構造器還是普通函數的區別罷了。此外再強調一點,這里我們討論的“友元”與“友函數”在名稱上的確借鑒自C++,但概念上卻有相當的差異。唯一與之相同的約定是,如果A是B的友函數,則 A就能訪問B的內部結構(例如私有成員)。

要實現這一點,我們得用Atom()把這兩個函數聯接起來。

舉個例子來說,函數A內部有一個列表,記錄了x,y,z三種狀態。我們設定,有且只有函數B能修改之(當然A的一些內部的方法也能修改,但不是我們這里的主要問題),那么怎么辦呢?

 
function A() {
var state = {
x:
100, y: 1000, z:5,
set:
function(n, v) { this[n] = v }
}

// ...
}
function B() {
// 如何在這里修改A中的state?
}

很自然的想法是讓A公布一個方法出來。例如:

 
function A() {
var state = ...
// 公布一個方法
this.set = function(n, v) {
state.set(n, v);
}
}

這樣可以通過構建一個對象并用obj.set()來修改。或者我們將A整個的放在一個閉包里,再返回一個函數來做"修改state"的事情。但無論如何,我們只能做到“修改state",而無法做到“只有B能修改,其它位置都無法調用修改state的代碼”。

好吧。傳統觀念上的、終極的方法, 是我們將state從A()里面移出來。然后將A()與B()放在同一個閉包里面:

 
function() {
var state = { .... };
function A() { ... };
function B() {
// 這樣就保證僅有A與B是可以修改state的了。
}
}

問題是,在組織大型的代碼、類庫或類繼承樹時,我們可能無法保證A與B處于同一個函數,或者他們根本就不是一個人寫的,或者B會是將來的開發人員追加編寫的......等等如此,反正A不見得總能與B在一個函數上下文里。

解決這個問題的方法呢?使用Atom()來為A()與B()建立一個友元關系,使它們成為友函數(如果在對象繼承中,則可以實現為友類)。如下:

 
A = B = Atom();
A
= function(atom) {
return function() {
var state = { ... };
if (arguments[0]===atom) return state;
// ... 后續邏輯
}
}(A);
B
= function(atom) {
return function() {
var A_state = A(atom);
// 后續邏輯
}
}(B);

現在,在最初的時候,A和B都指向一個原子。在得到“真實的A()、B()"的同時,函數A()、B()各持有了atom的一個引用。此后,系統中就再也找不到atom了——沒有任何方法可以A、B之外再他們所使用的atom。

由于A()與B()各持有了一個atom,于是,當B()函數調用A(atom)時,A函數就絕對可以信任這是來自于B的調用,因此將state返回即可——當然也可以返回存取函數,或者別的什么東西。對于B()的調用A(atom),以及A()對arguments[0]進行的識別,一切都是安全的、不可能受外界其它任何因素影響的。

四、其它

1、同樣的方法,我們可以對更多個的函數、構造器或子系統(使用同一個閉包上下文的大段代碼塊)建立友元關系。

2、原子性是JavaScript對象的固有特性,使用Atom()函數主要是可以上述技巧在系統中具有明確的語義,這比隨處定義一個"{}"來得要好。

3、QoBean內部使用這一技術來構建類繼承關系,從而使子類可以訪問父類的特性,對非子類來說則完全隔離。

0
0
 
 
 

文章列表

文章標籤
全站熱搜
創作者介紹
創作者 大師兄 的頭像
大師兄

IT工程師數位筆記本

大師兄 發表在 痞客邦 留言(0) 人氣(0)