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

作者: aimingoo  發布時間: 2011-01-09 23:14  閱讀: 1205 次  推薦: 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
 
 
 

文章列表

arrow
arrow
    全站熱搜
    創作者介紹
    創作者 大師兄 的頭像
    大師兄

    IT工程師數位筆記本

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