我們知道,預設情況下,對一個函數前面使用new,可以構造出一個對象。每一個對象都有一個constructor屬性,這個constructor屬性指向構造出該對象的函數。例如,在Chrome下調試如下程式,很清楚的展示了這點:
然而事情並不是這麼簡單。再看下面的代碼:
很顯然,這個時候obj的constructor已經不再是建立它的函數,注意到obj.name也是undefined,因此修改建構函式的prototype的contructor並不會影響建構函式所產生的對象。真正的原因是:一個對象的constructor是它的建構函式的prototype.constructor,而每一個函數都有一個prototype,預設情況下,這個prototype有一個constructor屬性,指向的是它自己。 我覺得Javascript的設計本意是讓每個對象的constructor都指向自己的建構函式,然而有像上面的例子可以破壞這一點。另外,這樣的設計其實也不很完美,一個很大的問題就是在繼承的時候必須小心的維護constructor的指向。在最簡單的繼承中,可以把子類的建構函式的prototype設定為父類的一個執行個體,而父類的執行個體的constructor是父類的建構函式,從而子類的prototype的constructor是父類的建構函式,這就造成了子類的每個對象的建構函式都是父類的建構函式。這是很容易引起困惑的。
最後,再回到上一篇遺留下來的問題,上文談到Extjs官網給出的一個繼承Observable的例子:
Employee = Ext.extend(Ext.util.Observable, { constructor: function(config){ this.name = config.name; this.addEvents({ "fired" : true, "quit" : true }); // Copy configured listeners into *this* object so that the base class's // constructor will add them. this.listeners = config.listeners; // Call our superclass constructor to complete construction process. Employee.superclass.constructor.call(config) }});
這個例子給人的錯覺就是你可以重寫父類的constructor屬性,從而達到改變子類的建構函式的行為的效果。這對於Javascript基礎卻不深的人是一種誤導。 我們再仔細看下Ext.extend的原始碼:
extend : function(){ // inline overrides var io = function(o){ for(var m in o){ this[m] = o[m]; } }; var oc = Object.prototype.constructor; return function(sb, sp, overrides){ if(Ext.isObject(sp)){ overrides = sp; sp = sb; sb = overrides.constructor != oc ? overrides.constructor : function(){sp.apply(this, arguments);}; //注意這裡
} //以下省略………
}(), 請注意加註釋的那一行。extend如果檢測到overrides參數中有constructor屬性,也就是說子類試圖改寫父類的prototype的constructor的時候,就直接將子類設定為這個函數!這樣就達到效果了。不過我立刻發現這句檢測僅在這個if語句塊中,也就是extend的兩參數版本中有,那麼使用extend的另一個三參數版本這樣設定應該是無效的。 寫段代碼測試下:
<head> <title></title> <script type="text/javascript" src="ext-3.1.0/src/core/core/Ext.js"></script> <script type="text/javascript"> function MyClass() { this.id = 'class'; } function SubClass() { } // SubClass=Ext.extend(SubClass, MyClass, { constructor: function() {// this.overrideProperty = 'OK';// }// }); SubClass = Ext.extend(MyClass, { constructor: function() { this.overrideProperty = 'OK'; } }); var obj = new SubClass(); alert(obj.overrideProperty); </script></head>
兩種寫法,啟動並執行結果果然是第一種是undefined而第二種是OK。呃,名字相同的函數僅僅是參數寫法不同,執行的效果卻不相同,這個是有點出乎意料的。而且Extjs的官方文檔未對此有任何說明。以後大家還是盡量用兩參數版本的吧。
最後順便再看幾個運算式,
第一個是說函數的prototype的constructor是自己,這個上文已經談到,沒有問題;第二個是說函數也是一個對象,它是Function建構函式的一個對象,也還好理解;第三個是說Function建構函式本身一個對象,它還是Function建構函式的一個對象;最後一個其實是和第三個等價的,是說Function對象的建構函式是它自己…… 我確實有點想不明白Function是怎麼自己構造自己的?雞生蛋,蛋生雞?要再追究下去就應該涉及到Javascript語言的具體實現了吧,就此打住。
哎,Javascript這個語言本身還是有點複雜啊。要不是有了XMLHttpObject,讓它大紅大紫了下,否則它還在黑暗的角落裡暗自哭泣呢。
作為應用程式開發人員,或許我們自己寫程式的並不會用到這些底層的東西,但是,JS架構中卻大量的使用了這些進階特性,而且再詳盡的文檔也不可能把這些方法的作用、影響一一道來,所以開發人員還是需要盡量理解這些東西,以免死了還不知道怎麼回事。上面的Ext.extend函數就是一個例子。