ECMAScript規定了兩個特殊的內置對象:Object和Function。他們的特殊性在於,他們本身既是對象又是函數,而他們同時也是對象和函數的構造器。這種自己生自己的邏輯顯然違反人性,如果還停留在類的繼承的思想上,那么更加無法理解。
然而ECMAScript是基於原型鏈的,所以忘掉類的繼承,從原型鏈入手:原型鏈是對象的集合,每個對象都有內部屬性[[Prototype]](注1)指向另一個對象;當訪問對象某一屬性的時候,如果此屬性不為此對象的自身屬性(注2),則繼續去[[Prototype]]指向的對象上查找此屬性。[[Prototype]]形成的對象的鏈式集合即原型鏈。這里可以得出:原型鏈上的所有元素都是對象。
ECMASciprt規定:原型鏈必須是有限長度(注3),而且終點必須是null。現在終點是唯一的,那么原型鏈上倒數第二個元素是不是唯一的呢?ECMAScript沒有規定,但從實現上來看,是唯一的。因為原型鏈上所有的元素都是對象,所以倒數第二個元素應該是所有對象的基礎對象。這個對象在實現中只給出一個引用,就是Object.prototype。這里可以得出:原型鏈上有兩個元素是固定的,終點是null,倒數第二的元素是Object.prototype指向的對象(注4)。
那么倒數第三個元素是不是固定的呢?不是。從倒數第二個元素是Object.prototype來看,通過{}字面量和new Object()創建的對象都在倒數第三這個位置,即POJO都在倒數第三。另外還有兩個特例,一個是除內置函數之外的內置對象,如Math、JSON;一個是除Object之外的內置函數的prototype屬性指向的對象,如Function.prototype。這里可以得出:原型鏈上倒數第三的元素一般是POJO+Math/JSON+(Function/Array/String/Boolean/Number/Date/RegExp/Error).prototype。
倒數第三的位置出現了這么多的prototype,那么倒數第四的位置就好推測了,所有除Object之外的內置函數作為構造器調用(注5)時生成的實例對象都在倒數第四。其中需要注意的是,所有的內置函數本身是Function作為構造器調用生成的實例對象,所以都在這個位置。這里可以得出:原型鏈上倒數第四的元素一般是(Function/Array/String/Boolean/Number/Date/RegExp/Error)實例,其中包括(Object/Function/Array/String/Boolean/Number/Date/RegExp/Error),注意這個括號里面Object回來了。
原型鏈基本結構如下圖:
從圖上看來:
- array等非POJO對象在原型鏈上和他們的構造器屬於同一級別
- POJO在原型鏈上比他的構造器還靠后一個級別
參考文檔:ES5
注:
- 內部屬性是不開放給JS訪問的屬性,但現代瀏覽器已經可以通過__proto__屬性訪問和設置[[Prototype]]
- own property,即直接設置在此對象上的屬性
- 執行以下代碼感受下:
var a = {}; a.__proto__ = a;
- Object.prototype和基礎對象的關系好比快捷方式和應用程序,本身沒有任何關系,現在可以指向基礎對象,以后也可以指向其他對象。當然原則上是不允許的,基礎對象沒有引用內存會被回收,所以ECMAScript規定Object下的prototype屬性的writable和configuration特性都是false(特性的問題以后另起一篇)
- 假設func為一個函數,func()即作為函數調用(調用內部函數屬性[[Call]]),new func()即作為構造器調用(調用內部函數屬性[[Construct]])