1. 程式人生 > >淺談JS __proto__與prototype的聯絡與區別

淺談JS __proto__與prototype的聯絡與區別

學習JavaScript也有一段時日了,今天想談一下JS中一個比較重要的關鍵概念——原型(prototype)

什麼是原型?

W3School中是如此描述原型的:
“Every JavaScript object has a prototype. The prototype is also an object.
All JavaScript objects inherit their properties and methods from their prototype.”
我個人對這段話的理解是,原型prototype是每一個JS物件都有的一個屬性,而這個屬性是一個物件,所有JS物件都能從它們的prototype繼承方法和屬性。通過prototype,JS得以以多種方式實現面向物件中的繼承概念。

原型繼承鏈

JS的物件之間存在著一個叫做原型繼承鏈的關係。物件能夠從自己的prototype繼承屬性和方法,當物件需要呼叫不屬於自己的方法或是使用不屬於自己的屬性的時候,物件則會通過一個叫做__proto__(這是什麼我們隨後解釋)的屬性去尋找它的prototype,在其中尋找方法和屬性,而如果在它的prototype中沒能找到的話,又會去它的prototype的prototype中尋找方法和屬性,直到達到這條原型繼承鏈的終點為止。
關於這一點,W3School中的解釋如下:
“All JavaScript objects inherit the properties and methods from their prototype.
Objects created using an object literal, or with new Object(), inherit from a prototype called Object.prototype.
Objects created with new Date() inherit the Date.prototype.
The Object.prototype is on the top of the prototype chain.
All JavaScript objects (Date, Array, RegExp, Function, ….) inherit from the Object.prototype.”

__proto__和prototype的聯絡與區別

接下來就要講到__proto__和prototype的聯絡和區別何在了。首先,__proto__是什麼?
__proto__是JavaScript中物件例項所擁有的一個屬性,它指向的是該物件例項所對應的prototype物件。(需要注意的是,如上所述,Object.prototype是所有物件繼承的頂點,而它的__proto__的值為null)
__proto__與prototype的區別則在於,__proto__是物件固有的屬性,它指向的是該物件例項的建構函式的prototype(Object.prototype除外),而prototype是函式固有的屬性,其值是一個函式的原型物件。舉個例子:

function Test() {}
var t = new Test();
console.log(t.__proto__ === Test.prototype)//true
console.log(t.prototype)//undefined

此處,Test是一個函式,而t是通過Test構造出的Test的例項,t.__proto__指向的是Test.prototype,而t本身是沒有prototype這個屬性的。
用一個簡單的例子也許還不能說清楚__proto__和prototype的聯絡,那麼我們來看一張我珍藏許久的關於原型繼承鏈的圖片吧:
這裡寫圖片描述

Function Foo() {}
var f1 = new Foo();

下面我們來解釋一下這張圖。
如圖,Foo是一個Function型別物件,f1是通過Foo構造出的Foo的一個例項。
接著,如圖所示,f1.__proto__指向的是Foo.prototype,同時,Foo本身的prototype屬性自然也是指向Foo.prototype的(廢話),這與前面Test和t的例子是一樣的。圖中還有一個叫做constructor的屬性,這個屬性是prototype本身固有的一個屬性,它的意義是指向建構函式本身。
接著往下看,前面我們說過,prototype本身也是一個物件,它實際上也是一個物件例項,那麼它自然也是有__proto__屬性的,在此圖中,Foo.prototype.__proto__指向的是Object.prototype,這也與我們前面引用的W3School的解釋吻合:
“Objects created using an object literal, or with new Object(), inherit from a prototype called Object.prototype.
The Object.prototype is on the top of the prototype chain.”
Object.prototype是繼承鏈的頂點,所有物件除了函式以及通過Object以外的函式構造出的物件例項以外,它們的__proto__屬性都預設指向Object.prototype。
接著我們看Foo函式本身,Foo函式本身其實可以看做是通過Function()這個建構函式構造出的一個function例項,因此,Foo.__proto__指向的是Function.prototype;那麼Object呢?Object其實也是一個函式,它也同樣可以看做是Function的一個例項,所以,Object.__proto__也是指向Function.prototype的。
然後最特殊的則是Function這個函式,一方面,Function的prototype屬性指向的當然是Function.prototype;另一方面,Function.prototype是一切函式的原型物件,而Function也是一個函式,也就是說,它是本身的一個例項,(自我感覺很抽象很不可思議,不過JS就是設計出了這樣一個函式物件……..)所以它的__proto__指向的是Function.prototype。
最後,Function.prototype也不例外,它是Object的一個例項,它的__proto__屬性指向的也是Object.prototype。
上面這麼一大段話下來,是不是有點繞?那麼來總結一下這張圖反映出的繼承鏈關係,假設有如下的程式碼:

Foo.prototype.testValue = 2;
Object.prototype.method = function() {
    console.log('the method inherited from Object.prototype');
}
f1.testValue;//2
f1.method();//the method inherited from Object.prototype
f1.testTwo//undefined

這段程式碼的執行過程如下:
當要使用f1.testValue時,在f1中沒有這個屬性,於是通過f1的__proto__到Foo.prototype去尋找testValue,找到以後,得以使用;
同理,在下一行呼叫method方法,f1中也沒有這個方法,於是又通過f1.__proto__去尋找,這次在Foo.prototype中也沒有method方法,於是再通過Foo.prototype.__proto__到下一級Object.prototype中去尋找method,找到以後,結束遍歷;
再往下,testTwo屬性在任意一級的prototype中都找不到這個屬性,於是得到了undefined的結果。

總結:
1.__proto__是物件固有的屬性,它指向的是該物件例項的建構函式的prototype(Object.prototype除外)。
2.prototype是函式固有的屬性,其值是一個函式的原型物件。
3.__proto__與prototype的聯絡在於:當一個物件要查詢自己沒有的屬性或者方法時,它是通過__proto__屬性去查詢,而不是通過prototype屬性,物件例項本身是沒有prototype屬性的。