• <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前端

    前端要給力之:分解對象構造過程new()

    作者: aimingoo  發布時間: 2011-01-09 23:14  閱讀: 1166 次  推薦: 0   原文鏈接   [收藏]  

      本文討論JavaScript中的對象創建運算new。需要說明的是,本文所討論的“將new()過程分解為多個步驟”,并非一般js開發中的所須技巧,而是在js來構建OOP系統的必要技術。

      一、JavaScript構造器與構造過程的特點

      JavaScript中通過以下方式聲明和使用構造器:

    function MyObject() {
    this.xxx = 1;
    }
    MyObject.prototype.yyy
    = 2;

    obj1
    = new MyObject();
    obj2
    = new MyObject();

      其中xxx與yyy的不同在于:對于obj1和obj2來說,yyy是相同的屬性的不同引用,不同對象實例的初始值總是相同的;而xxx則是各自不同的引用,每個對象實例都不同。上例中使用1、2這樣的值類型數據,并不足以體現二者的區別。當我們而使用引用類型(例如數組),就很容易看出二者的區別來了。例如:

    function MyObject() {
    this.xxx = new Array();
    }
    MyObject.prototype.yyy
    = new Array();

    obj1
    = new MyObject();
    obj2
    = new MyObject();

    obj1.xxx.push(
    'abc');
    obj1.yyy.push(
    'abc');

    // 顯示1,0,表明obj2.xxx并沒有變化
    alert([obj1.xxx.length, obj2.xxx.length]);

    // 顯示1,1,表明obj2.yyy同時變化,與obj1.yyy是同一個數組
    alert([obj1.yyy.length, obj2.yyy.length]);

      使用原型的另一個必要在于繼承樹的建立。這一過程要求new運算符的參與,簡單說來,就是"子類.prototype"必須賦值為"new 父類()"所產生的一個實例。例如:

    function MyObjectEx() {
    }
    MyObjectEx.prototype
    = new MyObject();
    MyObjectEx.prototype.constructor
    = MyObjectEx;

      在這個過程中,最后面的是一行修正代碼。這行修正的必要性在于:new MyObject()所產生的實例(例如x)的屬性x.constructor總是指向MyObject,而在子類MyObjectEx()中,它應該是指向MyObjectEx的。

      OK。這就是全部的基礎知識,下面我們來分解這一過程。

      二、new()過程中的原型鏈維護

      new總是因為建立原型繼承樹而存在的,如果沒有new過程參與,則當obj = new MyObjecEx()時,我們無法通過instanceof運算: obj instanceof MyObject 

      來了解obj在繼承樹上的關系。

      但是許多人并不知道,事實上這一過程并不需要MyObject的參與。因為instanceof只檢查prototype鏈,并不檢查函數本身。舉例來說:

    P = {};

    X
    = function() {}
    Y
    = function() {}
    X.prototype
    = P;
    Y.prototype
    = P;

    obj
    = new Y();
    alert(obj
    instanceof X); // 顯示true

      在這個示例中,obj創建自Y(),但只因為X.prototype(或者它的原型鏈中)指向原型P,所以最后顯示obj也是X的一個實例。可見 instanceof的檢測與obj是否創建自函數X是無關的,并不檢查函數自身。

      換而言之,new()在上述過程中,只有“維護原型鏈”的作用。那么,我們事實上也可以手工來維護這個原型鏈。這一點,事實上也就是ECMA Script 5th中的Object.create()出現的原因。

      Object.create(O[, Properties]) returns an object with a prototype of O and properties according to Properties (as if called through Object.defineProperties).

       不考慮Properties的部分,那么Object.create可以用以下過程來描述:

    Object.create = function(O) {
    function F() {};
    F.prototype
    = O;
    return new F();
    }

      通過這個create方法,我們可以創建任意的、足夠長的原型鏈,然后把它“賦值給”某個構造器函數,使“原型鏈創建過程”與“構造器中的初始化過程”二者分解開來。例如:

    A = {};
    B
    = Object.create(A);
    C
    = Object.create(B);
    A.x
    = 1;
    B.y
    = 2;
    C.z
    = 3;

    function MyObject() {
    }

    function MyObjectEx() {
    }

    MyObject.prototype
    = B;
    MyObjectEx.prototype
    = C;

      這樣一來,我們沒有顯式地聲明MyObject()與MyObjectEx()之間的繼承關系,但B與C的原型關系維護了它們之間的類屬關系。因此:

    obj1 = new MyObject();
    obj2
    = new MyObjectEx();

    // 顯示true, obj2是MyObject()的子類的實例
    alert(obj2 instanceof MyObject);

    // 顯示false, z屬性不會出現在MyObject()的實例中
    alert('z' in obj1);

      三、new()過程中的構造器調用

      對于構造器MyObject()來說,在new()過程中會被調用一次。例如此前提到的:

    function MyObject() {
    this.xxx = 1;
    }

      new()過程將以剛剛創建的實例為this來調用MyObject(),這使得我們有機會為這個實例(this)做一些初始化操作。這個行為其實來自于最最早期的JavaScript設計。在1995年底發布的Netscapes Navigator 2.0 beta以及其后的NN2.0正式版中,這個最原始版本的JavaScript都還沒有“原型繼承”的設計,但已經有了new這個運算。這時所謂新對象的創建,就是不斷地為this賦值而已,只不過new會為產生的對象維護<obj>.constructor屬性。

      這件事是很容易做到的。在高版本中的Function已經提供了Function.call()和Function.apply()方法,能很方便的重現這一過程。因此:

    obj = new MyObject(x,y,z);

      就可以被分解為如下兩個步驟(fixed at 2010.12.30. note: 接受book_LoveLiness的意見,將constructor的改寫過程放在原型階段):

    obj = Object.create({
    constructor: MyObject
    });
    MyObject.call(obj, x, y, z);

      對于上述{constructor: MyObject},內部隱含了如下兩個過程:

    o = new Object();
    o.constructor
    = MyObject;

      當用戶使用自己的構造器來創建MyObject的原型鏈——例如MyObject()的父類是ExtObject(),而不是Object()的時候,就會用到這樣的分解了。

      即使對于最早期沒有call()/apply()方法,也沒有原型繼承的JavaScript,上述過程也可以分解為:

    obj = new Object();
    obj.constructor
    = MyObject;
    obj.constructor(a, b, c);

      當然,這一過程也就與原型繼承沒有了關系——我們假定這就是JavaScript 1.0的時代吧。

      四、構造過程分解的意義

      因為原型繼承(包括原型鏈維護)與構造器調用是可以分開的,所以我們事實上也可以只使用二者之一來創建任何復雜的過程。對于大型OOP框架來說, “是否需要維護原型鏈”是一個深入的話題。例如一些早期的OOP框架就不管不顧,只考慮子類對象對父類的“相似性”,而無視instanceof運算的效果,這樣的時代大概可以追溯到2004年以及之前。后來OOP框架意識JavaScript的原型系統的重要性,因此又回到正途上來,

       - 通過類似于Object.create()的方案,來保證原型鏈的有效性;又

       - 通過獨立的類創建或對象創建過程,來保證系統的可擴展性。

      這一切,就是我們現在的JS OOP的來源了。

      在一些具體的方法中,有許多變形的實現。在QoBean中的Unique()函數綜合地重現了整個過程(fixed at 2010.12.30,考慮到無args參數的情況):

    function Unique(obj, func, args) {
    function F() {}
    F.prototype
    = obj;
    return func ? func.apply(new F, args||[]) : new F;
    }

      對于任何一個對象A來說,可以用它為原型創建一個新的實例:

    x = Unique(A);

      或在創建之后,使用函數MyObject()來初始化:

    y = Unique(A, MyObject);

      或在上述初始化中使用特定的參數:

    z = Unique(A, MyObject, [a, b, c]);
    0
    0

    Web前端熱門文章

      Web前端最新文章

        最新新聞

          熱門新聞

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