• <noscript id="eom2a"><optgroup id="eom2a"></optgroup></noscript>
    <tt id="eom2a"><small id="eom2a"></small></tt>
    <input id="eom2a"></input>
  • <div id="eom2a"><small id="eom2a"></small></div>
    <td id="eom2a"><small id="eom2a"></small></td>
  • 您的位置:知識庫 ? Web前端

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

    作者: aimingoo  發布時間: 2010-12-29 16:41  閱讀: 751 次  推薦: 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

    Web前端熱門文章

      Web前端最新文章

        最新新聞

          熱門新聞

            黄色网_免费在线黄色电影_黄色成人快播电影_伦理电影_黄色片