本文轉載於 SegmentFault 社區
作者:forceddd
1.原型對象,實例對象與構造函數
如果想要深入理解原型鏈,首先需要明確知道什麼時原型對象,什麼是實例對象,以及原型對象,實例對象與構造函數三者之間的關係。
首先,實例對象是指通過關鍵字new,由構造函數構造出的對象實例對象可以有多個,並且實例對象會繼承它的原型對象上的所有屬性。
}varf1= newFoo; varf2= newFoo;
f1與f2就是由構造函數Foo通過關鍵字new創建的兩個實例對象。
同一個構造函數的實例對象的原型對象是同一個,是構造函數的prototype屬性,在實例對象上可以通過__proto__屬性獲取。即:
因此當我們在原型對象上添加屬性或方法時,所有的實例對象都會繼承該屬性或方法,我們可以將一些公用的方法定義在Foo.prototype上。
2.原型對象,實例對象和構造函數三者的關係
每個實例對象的原型對象上都有一個constructor屬性,這個屬性指向實例對象的構造函數,實例對象可以從它的原型對象上繼承該屬性。
三者的關係聽起來十分繞口,但是用代碼體現就很清晰了:
實例對象f1,構造函數Foo,原型對象Foo.prototype===f1.__proto__
3.Java的原型鏈規則
1.js中規定,所有的對象都有自己的原型對象,原型對象和和函數也是對象,所以他們也有自己的原型對象(__proto__),因此會形成原型鏈。
2.js中讀取屬性和方法的規則:js引擎會首先在對象本身查找屬性和方法,如果找不到就會沿著原型鏈逐層向上查找,最終找不到就會返回undefined。如果找到了就不會再繼續查找,所以當一個實例對象和它的原型對象上具有同名屬性或方法時,只會查找到對象本身的屬性或方法。
使用圖示可以更好地理解原型鏈:
}varf1 = newFoo;
以上這段代碼:
首先有實例對象,構造函數和原型對象
原型對象的constructor指向構造函數,實例對象繼承的constructor也是指向構造函數,實例對象和構造函數的__proto__和prototype指向同一個原型對象。
然後,因為Foo.prototype也是一個對象,他也具有自身的原型對象__proto__,由三者關係我們可以知道,Foo.prototype.__proto__.constructor應該指向Foo.prototype的構造函數,輸出結果是Object,即Foo.prototype.__proto__.constructor===Object,並且Object.prototype也應該有自己的原型對象,輸出為null。
所以有:
再然後,構造函數Foo和Object也是實例對象,他們也應該由自己的原型對象__proto__,而所有的函數都是由構造函數Function構造的,所以Object.__proto__===Function.prototype,Foo.__proto__===Function.prototype,Object.__proto__===Foo.__proto__,並且Function.prototype也有自己的原型對象__popto__,指向了Object.prototype,可以畫出:
原型鏈圖示
這就是一條完整的原型鏈了,從圖中我們可以發現:
Object.prototype是原型鏈的盡頭,再向上就是null了,null是無意義的,一次所有的對象都繼承了Object.prototype上的屬性和方法。
4.提一個小問題,我想讓Foo的所有實例對象都具有數組的屬性和方法,應該怎麼做呢?
通過上面的分析,我們知道,實例對象共用的屬性和方法都應該定義在了他們的原型對象上,所以數組的屬性和方法都在Array.prototype上,
實例對象f1,f2共用的屬性和方法來自Foo.prototype,所以我們可以:
Foo.prototype=Array.prototype
這樣f1,f2就繼承了數組的屬性和方法,但是我們需要注意一點,此時輸出f1.constructor,來觀察f1,f2的構造函數會發現它們已經成了Array,
這是因為constructor屬性同樣是從原型對象Foo.prototype上繼承而來的,所以當Foo.prototype上的constructor屬性發生變化時,實例對象f1的constructor也會變化。所以我們如果不想讓構造函數發生變化的話,需要重寫一次Foo.prototype的constructor屬性,
Foo.prototype=Array.prototype
Foo.prototype.constructor=Foo。
所以我們需要注意,在修改實例對象的原型對象之後,也要記得修改constructor屬性
SegmentFault 思否社區和文章作者展開更多互動和交流。