一文讀懂JavaScript原型鏈
前言
什么是原型鏈
每個對象(Object)都有一個私有屬性指向另一個名為原型(prototype)的對象。原型對象也有一個自己的原型,層層向上直到一個對象的原型為 null。根據(jù)定義,null 沒有原型,并作為這個原型鏈(prototype chain)中的最后一個環(huán)節(jié)。
說明
- __proto__實(shí)際為[[Prototype]]屬性的訪問器,為了便于理解,本文以屬性代稱其訪問器實(shí)質(zhì)
- 這里不使用class表達(dá)式是因?yàn)閏lass表達(dá)式實(shí)際上是特殊的函數(shù),更類似于將多個操作融合后的語法糖。使用Function來理解更為直觀
- 所有代碼均已在Chrome瀏覽器v125.0.6422.142經(jīng)過結(jié)果驗(yàn)證
一、名詞解釋
在開始了解原型鏈之前,先介紹兩個名詞prototype以及__proto__,舉一個簡單的例子更為直觀
定義一個函數(shù)Foo,而后創(chuàng)建一個Foo的實(shí)例對象o1。
function Foo(){}
const o1 = new Foo()
(一)、原型對象(prototype)
[!NOTE]
所有的函數(shù)都是對象,擁有獨(dú)有屬性prototype
prototype原型對象,是函數(shù)的獨(dú)有屬性。該屬性指向一個對象。當(dāng)函數(shù)(如:Foo)被實(shí)例化成一個對象(如:o1)后,實(shí)例對象(o1)可以訪問到函數(shù)(Foo)的原型對象(prototype)
如:在Foo的prototype上增加屬性propA,其值為'p1',可以發(fā)現(xiàn)在o1上也可以獲取到屬性propA,其值同樣為'p1'
Foo.prototype.propA = 'p1'
o1.propA // 'p1'
那么o1是如何獲取到Foo.prototype上的方法的呢,這就要介紹另一個概念,隱式原型__proto__
(二)、隱式原型(__proto__)
[!Note]
所有非內(nèi)置對象都是函數(shù)的實(shí)例,擁有獨(dú)有屬性__proto__
__proto__隱式原型,是對象的獨(dú)有屬性。對象(如:o1)的__proto__屬性指向其構(gòu)造函數(shù)(如:Foo)的原型對象( prototype )。所有非內(nèi)置對象都是函數(shù)的實(shí)例,同時擁有一個構(gòu)造函數(shù)。如:對象o1的構(gòu)造函數(shù)為Foo
內(nèi)置對象如:Function、Date、Array、Object、Math、JSON等。
如:o1上訪問到的屬性propA實(shí)際上是其構(gòu)造函數(shù)Foo的prototype的屬性propA。這里將Foo.prototype.propA設(shè)置為一個對象,來防止因基本類型的值比較方式導(dǎo)致結(jié)論誤差
o1.constructor === Foo // true
Foo.prototype.propA = {}
o1.propA // {}
o1.propA === o1.__proto__.propA // true
o1.__proto__.propA === Foo.prototype.propA // true
二、原型鏈
在介紹何為prototype以及__proto__后,接下來就開始介紹原型鏈了。仍以之前使用的例子來進(jìn)行原型鏈的介紹
function Foo(){}
const o1 = new Foo()
(一)、實(shí)例化關(guān)系
首先一起來分析例子的實(shí)例化關(guān)系??稍贑hrome中測試如下代碼
o1.constructor === Foo // true
Foo.constructor === Function // true
Function.constructor === Function // true
根據(jù)驗(yàn)證結(jié)果可分析出如下圖結(jié)論
圖片
- o1的數(shù)據(jù)類型是對象,是函數(shù)Foo的實(shí)例化
- Foo的數(shù)據(jù)類型既是函數(shù)也是對象,也是Function的實(shí)例化
- Function既是函數(shù)也是對象,其是自身Function的實(shí)例化
(二)、獨(dú)有屬性分析
在實(shí)例化關(guān)系的基礎(chǔ)上,繼續(xù)分析每一級的屬性關(guān)系。其中綠色代表函數(shù)獨(dú)有屬性,紅色代表對象獨(dú)有屬性
圖片
- o1的數(shù)據(jù)類型為對象,擁有獨(dú)有屬性__proto__,由于prototype是函數(shù)獨(dú)有屬性,所以o1上的prototype為undefined
- Foo的數(shù)據(jù)類型既是函數(shù)也是對象,所以其同時擁有屬性prototype和__proto__
- Function的數(shù)據(jù)類型既是函數(shù)也是對象,所以其同時擁有屬性prototype和__proto__
(三)、隱式原型引用關(guān)系
[!NOTE]
對象的隱式原型(__proto__)屬性指向其構(gòu)造函數(shù)(constructor)的原型對象(prototype)
圖片
- o1.__proto__指向其構(gòu)造函數(shù)Foo的原型對象( prototype )
- Foo.__proto__的指向其構(gòu)造函數(shù)Function的原型對象( prototype )
- 由于Function的構(gòu)造函數(shù)是其自身,所以Function.__proto__指向其自身的原型對象( prototype )
由于函數(shù)的原型對象( prototype )屬性的數(shù)據(jù)類型為對象,因此同樣具有對象的獨(dú)有屬性__proto__。如下圖
圖片
默認(rèn)情況下,對象隱式原型( __proto__ )指向其構(gòu)造函數(shù)的原型對象( prototype ),那么Foo.prototype和Function.prototype的構(gòu)造函數(shù)有指向哪里呢?
[!NOTE]
所有函數(shù)的原型對象( prototype )的構(gòu)造函數(shù)均指向其自身
可通過以下測試代碼進(jìn)行驗(yàn)證
Foo.prototype.constructor === Foo // true
Function.prototype.constructor === Function // true
內(nèi)置函數(shù)對象的原型對象( prototype ),其隱式原型( __proto__ )也指向其自身
RegExp.prototype.constructor === RegExp // true
Date.prototype.constructor === Date // true
Map.prototype.constructor === Map // true
Array.prototype.constructor === Array // true
Number.prototype.constructor === Number // true
Object.prototype.constructor === Object // true
然而一些內(nèi)置對象由于其沒有函數(shù)特征,所以其原型對象( prototype )屬性為undefined,其自身的constructor指向Object。
Math.prototype // undefined
Math.constructor === Object // true
JSON.prototype // undefined
JSON.constructor === Object // true
言歸正傳,由于所有函數(shù)的原型對象( prototype )的構(gòu)造函數(shù)均為其自身,則如若Foo.prototype.__proto__指向其構(gòu)造函數(shù)的prototype,即Foo.prototype.__proto__指向Foo.prototype。那么原型鏈的查找將進(jìn)入無限循環(huán)。為了避免這個問題,則將所有函數(shù)原型對象( prototype )的隱式原型(__proto__)均指向Object.prototype。
圖片
可通過以下代碼進(jìn)行驗(yàn)證:
Foo.prototype.__proto__ === Object.prototype // true
Function.prototype.__proto__ === Object.prototype // true
這里欠缺的有兩個點(diǎn):
- Object既是函數(shù)也是對象,所以其擁有對象的獨(dú)有屬性( __proto__ ),那么其隱式原型( __proto__ )指向哪里
- Object.prototype的數(shù)據(jù)類型為一個對象,如果其隱式原型( __proto__ )仍指向Object.prototype,那么原型鏈的查找將進(jìn)入無限循環(huán),那么其指向哪里
圖片
針對第1點(diǎn),Object自身既是函數(shù)又是對象,其作為對象的獨(dú)有屬性隱式原型( __proto__ )應(yīng)指向其構(gòu)造函數(shù)的原型對象( prototype )。Object對象的構(gòu)造函數(shù)為Function,所以其隱式原型( __proto__ )指向Function.prototype。
可通過以下測試代碼進(jìn)行驗(yàn)證:
Object.constructor === Function // true
Object.__proto__ === Function.prototype // true
因此其指向關(guān)系如下圖:
圖片
針對第2點(diǎn),文章起始什么是原型鏈已經(jīng)給出了定義。
原型對象也有一個自己的原型,層層向上直到一個對象的原型為 null
因此其最終指向?yàn)閚ull
圖片
三、總結(jié):
對象通過隱式原型( __proto__ )屬性指向其構(gòu)造函數(shù)的原型對象( prototype ),進(jìn)而通過原型對象( prototype )的隱式原型( __proto__ )屬性指向更高層級的原型對象( prototype ),最終指向null而停止所形成的鏈條,則稱其為原型鏈。