Constructor
JS裡沒有class,也就沒有class裡的構造函數,那麼object是怎麼被創建的呢?用構造器:constructor。constructor其實就是Function,因此本身也是object。開頭的function Base(){}就是一個構造器,var b = new Base()就是用這個構造器(通過關鍵字new)創建了一個叫b的object。至此我們可以得出結論,開頭的兩行代碼至少創建了2個object:一個是Base,類型為function的object,一個是base,類型為object的object。
Function()和Object()
這是兩個重要的預定義好的構造器。一切function(比如開頭的Base())都是由Function()構造出來的;而Object的prototype將會被所有object繼承,下面會講到。
Function的創建過程
當執行function Base(){this.a = 1}時,相當於var Base = new Function(“this.a = 1”),也就是說,這行代碼本身,將使用預定義好的Function() constructor,來構造一個function型object(即Base)出來。在這個創建過程中,js將做哪些事呢?
1, 首先當然會創建一個object起來,Base指向這個object。typeof 這個object = “function”
2, 給Base附上__proto__屬性,讓它等於Function這個構造器的prototype(也是預定義好的)。這是很重要的一步,也是規律性的一步。(規律:)在執行任意類似varx = new X()時,都會把X的prototype賦給x的__proto__,也就是說,x.__proto__和X.prototype此時會指向同一個對象。
3, 為Base創建call屬性,該屬性是個function。因此我們可以這樣寫:Base.Call()
4, 為Base創建Construct屬性,該屬性也是個function。在執行var base = new Base()時,即會調用這個Construct屬性。
5, 為Base創建Scope,Length等屬性,略。
6, 為Base創建prototype屬性:先用new Object()創建一個對象,為這個對象創建一個屬性叫constructor,該屬性值設置為Base。再把Base的prototype設置為這個新創建的對象。偽代碼如下:
var x = new Object();
x.constructor = Base;
Base.prototype = x;
先把關注點放到2和6。
__proto__和prototype
從2可以看出來,任意一個用構造器構造出來的object(包括Objects和Functions),都會有__proto__屬性,指向該構造器的prototype屬性。注意__proto__是個私有屬性,在IE上是看不到的,我用的是chrome,可以看到。
從6可以看出,任意一個用new Function()構造出來的functions,都會有prototype屬性,該屬性是用new Object()構建出來的,初始公開屬性只有一個constructor。
原型鏈
再來分析下第6步的偽代碼,也就是為function創建prototype的這一步:
var x = new Object(); // 參見2中的規律,會有x.__proto__= Object.prototype。
x.constructor = Base;
Base.prototype = x;
此時我們用Base()構造一個對象出來:
var base= new Base(); // 參見2中的規律,會有base.__proto__ = Base.prototype,也就是 = x。 // 因此有base.__proto__.__proto__ = x.__proto__ // 而x.__proto__ = Object.prototype(見上一個代碼片段) // 所以,base.__proto__.__proto__ = Object.prototype.
__proto__.__proto__,這就是傳說中JS對象的原型鏈!由於用Function()創建構造器時的關鍵的第6步,保證了所有object的原型鏈的頂端,最終都指向了Object.prototype。
Property Lookup
而我們如果要讀某個object的某個屬性,JS會怎麼做呢?
比如有個object叫xxx,我們執行alert(xxx.a),也就是讀取xxx的a屬性,那麼JS首先會到xxx本身去找a屬性,如果沒找到,則到xxx.__proto__裡去找a屬性,由此沿著原型鏈往上,找到即返回(沒找到,則返回undefined)。可以來看個例子:
上圖得知:base本身是沒有constructor屬性的,但是base.constructor確實能返回Base這個函數,原因就在於base.__proto__有這個屬性。(base.__proto__是啥?就是Base.prototype,上面構建Function的第6步的偽代碼裡,為Base.prototype.constructor賦值為Base本身。)
Object作為“基類”
另外,由於任意object的原型鏈的頂端都是Object.prototype。所以,Object.prototype裡定義的屬性,就會通過原型鏈,被所有的object繼承下來。這樣,預定義好的Object,就成了所有對象的“基類”。這就是原型鏈的繼承。
看上圖,Object.prototype已經預定義好了一些屬性,我們再追加一條屬性叫propertyA,那麼這個屬性和預定義屬性一樣,都可以從base上讀到。
原型繼承
已經得知,
對於 var xxx =new Object(); 有xxx.__proto__= Object.prototype;
對於 var xxx =new Base(); 有xxx.__proto__.__proto__= Object.prototype;
看上去很像什麼呢?從c#角度看,很像Base是Object的子類,也就是說,由Base()構造出來的object,比由Object()構造出來的object,在原型鏈上更低一個層級。這是通過把Base.prototype指向由Object()創建的對象來做到的。那麼自然而然,如果我想定義一個繼承自Base的構造器,只需把改構造器的prototype指向一個Base()構造出來的對象。
function Derived(){}
var base = new Base();
Derived.prototype = base;
var d = newDerived(); //很容易推算出:d.__proto__.__proto__.__proto__ = Object.prototype.
推算過程:d.__proto__指向Derived.prototype,也就是base;則__proto__.__proto__指向base.__proto__,也就是Base.prototype,也就是某個new object()創建出來的東東,假設是o;則__proto__.__proto__.__proto__指向o.__proto__,也就是Object.prototype。
回答開頭的問題,以及幾個新的問題
那兩行代碼至少創建了三個對象:Base、base、Base.prototype。順便說說,base是沒有prototype屬性的,只有function類型的object,在被構建時才會被創建prototype屬性。
d.constructor會返回什麼呢?
構造器Base()和Derived()裡都是空的,如果有代碼,將會怎麼被執行呢?
……
待續。見下篇