以問題開始:
function Base(){}var base = new Base()
上面兩行代碼會建立幾個對象(object)?
要回答這個問題,先明確一下Javascript裡object的概念。
Objects
在Javascript裡,幾乎一切都是object(Arrays、Functions、Numbers、Objects……),而沒有C#裡的class的概念。object的本質是一個name-value pairs的集合,其中name是string類型的,可以把它叫做“property”,value包括各種objects(string,number,boolean,array,function…),指的是property的值。
typeof
既然object包含Arrays、Functions、Numbers、Objects……,那怎麼區分這些呢?答案是typeof。 typeof返回一個字串,如typeof(Null) = “object”,typeof(false) = “Boolean”, typeof(1) = “number”。既然總是返回字串,那麼對於typeof (typeof x),不管x是什麼,總是返回”string”。
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()裡都是空的,如果有代碼,將會怎麼被執行呢?
……
待續。見下篇