用另一種視角去理解JavaScript的原型鏈,明白__proto__跟prototype的本質。 ...
一、何為原型鏈
原型是一個對象,當我調用一個對象的方法時,如果該方法沒有在對象裡面,就會從對象的原型去尋找。JavaScript就是通過層層的原型,形成原型鏈。
二、誰擁有原型
任何對象都可以有原型,當我們創建對象的時候,會自動為對象添加一個屬性,這個屬性就是原型,我們無法訪問到他,但在firefox和chrome中可以通過一個非標準的屬性__proto__(雙下劃線)來訪問到原型(或通過Object.getPrototypeOf來訪問)。
三、理解原型鏈
我們先從以下代碼入手
var foo = {};
console.log(foo.toString()); // [object Object]
console.log(foo.__proto__); // object { ... } 這裡指向Object.prototype
foo裡面明明沒有toString方法,但我卻能調用,這就是原型鏈的作用。當我調用foo.toString時,由於在裡面找不到toString方法,那麼我從__proto__屬性裡面去找,找到後並調用。上面的代碼中我們就是從Object.prototype中找到了toString方法。你可能會很困惑,prototype是什麼?我們不要被prototype所迷惑,他只是一個存放屬性的容器而已,你可以如下這樣做來實現繼承(但儘量不要這麼做)
function Bar() {}
Bar.test = {
say: function () {
console.log('say test');
}
}
var foo = new Bar();
foo.say(); // 報錯
// 改變繼承的對象
foo.__proto__ = Bar.test;
foo.say() // say test
在上面的代碼中我們通過new的形式來創建一個對象,在new的過程中對象會將__proto__指向函數的prototype,由於prototype中是沒有say函數的,所以調用會報錯,但是之後我們強行改變了繼承的對象,將foo的繼承對象改為Bar.test,所以我們就能調用say函數了。
我想你已經明白個大概了,prototype事實上並沒有什麼特殊的,硬要說有什麼特殊的話,他只是被JavaScript預設為原型屬性的存放點而已,他本質上只是個對象,原型鏈的重點就在於__proto__,你可以試著把__proto__當作橋梁,當我在對象內部找不到屬性時,我就會通過這座橋梁到對面的對象里去尋找屬性,直到找到為止或者對象里沒有橋梁時才停下來。JavaScript就是通過這樣的方式來形成原型鏈,實現繼承的關係。
最後說一下,__proto__只是方便我們查看對象的原型而已,大家不要通過修改__proto__來實現繼承的關係,而是要用如構造函數之類的方式來實現繼承,這個我會放到以後的文章去說。
(ps:可能有動手能力強的同學會自己去測試,發現__proto__裡面也有__proto__,一直迴圈下去,無窮無盡,但事實上你去獲取的時候你會發現Object.__proto__.__proto__.__proto__的值是null,也就是沒有原型。)