原型的含義是指:若是構造器有個原型對象A,則由該構造器建立的實例(Object Instance)都必然複製於A。「「在JavaScript中,對象實例(Object Instance)並無原型,而構造器(Constructor)有原型,屬性’<構造器>.prototype’指向原型。對象只有「構 造自某個原型」的問題,並不存在「持有(或擁有)某個原型」的問題。」」如何理解這一句話?函數
代碼1:ui
02 |
var name = "stephenchan" ; |
05 |
alert( "Hello World!" ); |
08 |
var obj = new myFunc(); |
12 |
alert(obj.constructor); |
14 |
alert(obj.constructor == myFunc); |
17 |
alert(myFunc.prototype); |
19 |
alert(myFunc.constructor); |
21 |
alert(myFunc.prototype.constructor == myFunc); |
構造器與函數的概念是一致的,即代碼1中,myFunc就是一個構造器,由於經過new myFunc()就能夠構造出一個對象實例了。所以,」alert(obj.prototype)」輸出undefined說明了對象實例是沒有原型的,」alert(myFunc.prototype)」輸出[object Object]說明了構造器有原型,而「obj.constructor==myFunc」返回true說明obj的構造器是myFunc。
原型其實也是一個對象實例。再強調一下原型的含義是:若是構造器有個原型對象A,則由該構造器建立的實例 (Object Instance)都必然複製於A,並且採用的讀遍歷機制複製的。讀遍歷複製的意思是:僅當寫某個實例的成員時,將成員的信息複製到實例映像中。即當構造 一個新的對象時,新對象裏面的屬性指向的是原型中的屬性,讀取對象實例的屬性時,獲取的是原型對象的屬性值。而當對象實例對一個屬性進行寫操做時,纔會將 屬性寫到新對象實例的屬性列表中。this
![prototype_build prototype_build](http://static.javashuo.com/static/loading.gif)
圖1 JavaScript使用讀遍歷機制實現的原型繼承spa
代碼2:prototype
01 |
Object.prototype.value = "abc" ; |
02 |
var obj1 = new Object(); |
03 |
var obj2 = new Object(); |
圖1是對代碼2的描述,說明讀遍歷機制是如何在成員列表以致原型中管理對象成員的。只有對屬性進行第一次寫操做的時候,纔會在對象的成員列表中添加 該屬性的記錄。當obj1和obj2經過new來構造出來的時候,仍然是一個指向原型的引用,在操做過程當中也沒有與原型相同大小的對象實例建立出來。這樣 的讀遍歷就避免在建立新對象實例時可能的大量內存分配。當obj2.value屬性被賦值爲10的時候,obj2則在其成員表中添加了一個value成 員,並賦值爲10,這個成員表就是記錄了obj2中發生了修改的成員名、值與類型。這張表是否與原型一致並不重要,只須要遵循兩條規則:(1)保證在讀取 時被首先訪問到。(2)若是在對象中沒有指定的屬性,則嘗試遍歷對象的整個原型鏈,直到原型爲空或找到該屬性。代碼2中的delete操做是將obj2成 員表中的value刪除了,所以在讀取obj2的value屬性的時候就遍歷到Object中讀取。code
函數的原型老是一個標準的、系統內置的Object()構造器的實例,不過該實例建立後constructor屬性總先被賦值爲當前的函數。對象
代碼3:繼承
05 |
alert(MyObject.prototype.constructor == MyObject); |
08 |
delete MyObject.prototype.constructor; |
12 |
alert(MyObject.prototype.constructor == Object); |
13 |
alert(MyObject.prototype.constructor == new Object().constructor); |
從代碼3中能夠看出,MyObject.prototype其實與一個普通對象」new Object()」並無本質的區別,只是在建立時將constructor賦值爲當前函數MyObject。而後,當一個函數的prototype有意 義以後,它就搖身一變成了一個「構造器」,這時,若是用戶試圖用new運算符建立它的實例時,那麼引擎就會再構造一個新的對象,並使這個新對象的原型連接 向這個prototype屬性就能夠了。所以,函數與構造器並無明顯的界限。ip
一個構造器產生的實例,其constructor屬性默認老是指向該構造器,而究其根源,則在於構造器(函數)的原型的constructor屬性指向了構造器自己。
代碼4:內存
3 |
var obj = new MyObject(); |
5 |
alert(obj.constructor == MyObject); |
7 |
alert(MyObject.prototype.constructor == MyObject); |
因而可知,JavaScript事實上已經爲構造器維護了原型屬性,所以咱們能夠經過實例的constructor屬性來找到構造器,並進而找到它 的原型「obj.constructor.prototype」。可是,若是咱們把構造器的原型修改了的話,會出現什麼狀況呢?如代碼5,咱們把 MyObjectEx的原型修改了。
代碼5:
3 |
function MyObjectEx() { |
5 |
MyObjectEx.prototype = new MyObject(); |
6 |
var obj1 = new MyObject(); |
7 |
var obj2 = new MyObjectEx(); |
8 |
alert(obj1.constructor == obj2.constructor); |
9 |
alert(MyObjectEx.prototype.constructor == MyObject.prototype.constructor); |
在代碼5中,obj1和obj2是由不一樣的兩個構造器產生的實例,分別是MyObject和MyObjectEx。然而,咱們看到,代碼5中的兩個alert都會輸出true,便是說,由兩個不相同的構造器產生的實例(代碼5中的MyObject和MyObjectEx),它們的constructor屬性卻指向了相同的構造器, 是否是很詭異?這個正確是體現了原型繼承中出出現的「原型複製」了。要注意,MyObjectEx的原型是由MyObject構造出來的對象實例,即 obj1和obj2都是從MyObject原型中複製出來的對象,所以它們的constructor指向的都是MyObject。那麼怎麼解決這個問題?
代碼6:
02 |
this .constructor = arguments.callee; |
04 |
MyObject.prototype = new Object(); |
06 |
function MyObjectEx() { |
07 |
this .constructor = arguments.callee; |
09 |
MyObjectEx.prototype = new MyObject(); |
11 |
obj1 = new MyObjectEx(); |
12 |
obj2 = new MyObjectEx(); |
代碼6與代碼5中的主要區別就是在於,在MyObjectEx的初始化中正確地維護了constructor屬性,使當前的constructor屬性指向了調用的構造器。代碼6所描述的繼承關係如圖2:
![proto proto](http://static.javashuo.com/static/loading.gif)
圖2 構造器原型鏈與內部原型鏈
其中有[proto]屬性中一個對象的私有屬性,用於正確維護對象的內部原型鏈,在Firefox中能夠經過[__proto__]來訪問,這個後 面再討論。咱們能夠看到MyObjectEx的構造器是MyObject的對象實例,而MyObject的構造器是Object的對象實例。
接代碼6:
2 |
alert(obj.constructor === MyObject); |
3 |
alert(obj1.constructor === MyObjectEx); |
4 |
alert(obj.constructor === obj1.constructor); |
能夠看到,obj和obj1從不一樣的構造器產生的實例,其constructor屬性已經可以正確地指向相應的構造器,這個是因爲在對象實例初始化 的時候的賦值語句」this.constructor = arguments.callee;」。你可能會疑問爲何不採用下面這種方式來實現:
1 |
MyObjectEx.prototype = new MyObject(); |
2 |
MyObjectEx.prototype.constructor = MyObjectEx; |
這樣雖然能使obj1和obj2的constructor屬性正確地指向了MyObjectEx,可是,這樣同時也使得MyObjectEx的原型 對象(MyObject構造的實例)的constructor屬性無法往父代原型追溯。由於當MyObjectEx的原型對象想經過 constructor屬性來獲取到MyObject構造器時,會發現獲取到的是MyObjectEx的構造器,而不是期待的MyObject的構造器。
咱們能夠經過下面的語句來驗證代碼6是否是的確是如圖2的關係鏈:
1 |
alert(obj1.constructor === MyObjectEx); |
2 |
alert(MyObjectEx.prototype instanceof MyObject); |
3 |
alert(MyObjectEx.prototype.constructor === MyObject); |
4 |
alert(MyObject.prototype instanceof Object); |
5 |
alert(MyObject.prototype.constructor === Object); |
6 |
alert(obj1.constructor.prototype.constructor.prototype.constructor === Object); |
好了,剛纔上面提到了有一個不可訪問的屬性[proto],這個屬性是JavaScript引擎內部維護的原型鏈屬性,這個屬性在Firefox裏 面能夠經過[__proto__]來訪問的,通常狀況下,[proto]屬性指向的和prototype屬性同樣,指向的都是原型對象,兩個有什麼不一樣後 面會有講述。
2 |
alert(obj.__proto__ instanceof Object); |
3 |
alert(obj1.__proto__ instanceof MyObject); |
4 |
alert(obj2.__proto__ instanceof MyObject); |
這個[proto]屬性是JavaScript內部維護的,外部是不可訪問的,由這個屬性所維護的原型鏈爲內部原型鏈,與由prototype和constructor維護的外部原型鏈。那麼這兩條原型鏈有什麼區別呢?簡單來講就是,經過prototype和constructor來維護的外部原型鏈是開發人員本身代碼中回溯時用到的,而經過[proto]維護的內部原型鏈是JavaScript原型繼承機制實現所須要的。 具體來講,外部原型鏈就是作這種 事:」alert(obj1.constructor.prototype.constructor.prototype.constructor === Object);」,也就是說當咱們開發人員想要本身去回溯整個原型繼承的結構鏈時,也只會在咱們開發人員寫代碼時纔出現經過prototype和 constructor來訪問外部原型鏈。而內部原型鏈,這個比較有意思,在[圖1 JavaScript使用讀遍歷機制實現的原型繼承],咱們看到,當咱們訪問一個對象實例的屬性時,它若是發如今其成員列表中沒有該屬性,即會去訪問原型 的成員列表,把原型的默認值讀取出來,也就是說,這個在原型鏈中回溯來查詢成員屬性的過程,只會在內部原型鏈中進行,這個過程是由JavaScript引 擎本身去維護的,開發人員無法干涉。來看看代碼,我以爲這個仍是至關有意思的:
接代碼6:
01 |
alert(obj.__proto__ instanceof Object); |
02 |
alert(obj1.__proto__ instanceof MyObject); |
03 |
alert(obj2.__proto__ instanceof MyObject); |
05 |
MyObjectEx.prototype.value = "Hello World!" ; |
09 |
function MyObjectEx2() {} |
10 |
MyObjectEx.prototype = new MyObjectEx2(); |
最後的1個alert輸出的」Hello World!」,有意思吧。即便我在上面把MyObjectEx的原型對象改變成新的MyObjectEx2,可是在obj1和obj2中的 [proto]屬性依然指向的是原來的MyObject構造的對象實例,也就是說內部訪問屬性時是經過[proto]來回溯原型鏈的,而不是經過 prototype的(並且對象實例也沒有prototype屬性),這個就是內部原型鏈體現的威力。