前言
首先原型幔崖、原型鏈,算是前端進(jìn)階里面必不可少渣淤,十分重要的一塊了赏寇。由于這塊特別繞,所以面試官很喜歡用這一塊來辨別你的底層知識(shí)掌握的怎么樣价认。用的第三方框架嗅定,庫里面,很多功能模塊化了用踩,但大部分功能都繼承自一個(gè)基類渠退。既然涉及到繼承,那也必不可少得先了解原型鏈脐彩,所以原型鏈確實(shí)重中之重碎乃。
為什么大家對原型,原型鏈子會(huì)感到“懵”跟“繞”
其本質(zhì)是因?yàn)椋?strong>大家都沒理清楚__proto__
惠奸、prototype
梅誓、constructor
三者的聯(lián)系。所以很多人在看這一塊知識(shí)的時(shí)候佛南,剛開頭看可能還能理解证九,看久了就懵了,因?yàn)榇a中充斥著各種x.__proto__.__proto__
共虑,x.__proto__.constructor.prototype
愧怜,x.prototype.__proto__
等等,這當(dāng)然會(huì)懵掉妈拌。所以我們要理解原型拥坛、原型鏈?zhǔn)鞘裁磁畹欢ㄒ雀忝靼祝?code>__proto__、prototype
猜惋、constructor
這三個(gè)到底是個(gè)什么東西丸氛,再弄明白它們?nèi)齻€(gè)是什么聯(lián)系。
下面我會(huì)用比較通俗的話來解釋著摔,帶著大家更好的理解原型缓窜,原型鏈?zhǔn)鞘裁矗ㄒ驗(yàn)闉榱舜蠹腋玫睦斫猓杂行┑胤娇赡軙?huì)稍微有點(diǎn)不恰當(dāng)谍咆,敬請見諒)禾锤。
為了更好的理解,我們用以下變量作為例子跟話術(shù):
-
People
為構(gòu)造函數(shù) -
person
為由People
實(shí)例出來的一個(gè)對象 -
Object
為構(gòu)造所有對象的頂級基類構(gòu)造函數(shù) -
Function
為構(gòu)造所有函數(shù)的頂級基類構(gòu)造函數(shù)
__proto__
這個(gè)屬性可以通俗的理解成摹察,所有對象都擁有的一個(gè)私有屬性(函數(shù)也是一種特殊的對象恩掷,所以構(gòu)造函數(shù)也會(huì)有這個(gè)屬性)。所以我們會(huì)看到person.__proto__
供嚎、People.prototype.__proto__
黄娘、People.__proto__
、Object.__proto__
克滴、Function.__proto__
等描述逼争。
prototype
這個(gè)屬性可以通俗的理解成,專屬于函數(shù)自身的一個(gè)屬性(可用hasOwnProperty
驗(yàn)證)劝赔,所以實(shí)例出來的對象不會(huì)有誓焦,只有函數(shù)、構(gòu)造函數(shù)會(huì)有望忆。我們通常都會(huì)把構(gòu)造函數(shù).prototype
看做一個(gè)整體罩阵,它代表的是竿秆,這個(gè)函數(shù)的prototype
里启摄,所有的屬性方法等(People.prototype
代表People.prototype
這個(gè)整體里,所有的屬性與方法)幽钢。所以我們會(huì)看到person.__proto__.prototype
歉备、People.prototype
、Object.prototype
匪燕、Function.prototype
等描述蕾羊,但一定不會(huì)看到實(shí)例.prototype
(person.prototype
)。
例外:
- 箭頭函數(shù)沒有
prototype
帽驯,箭頭函數(shù)也不能拿來做構(gòu)造函數(shù)- 使用
bind
方法創(chuàng)造出來的副本函數(shù)也沒有prototype
這兩個(gè)是例外龟再,大家記得就好,但不影響我們的理解尼变。
constructor
這個(gè)屬性也可以通俗的理解成利凑,所有對象都擁有的一個(gè)屬性浆劲。可以用對象.constructor.name
來查看當(dāng)前構(gòu)造函數(shù)的名字是什么(person.constructor.name
返回People
哀澈,因?yàn)?code>person由People
構(gòu)造實(shí)例而來)牌借。所以我們也會(huì)看到person.constructor
、People.prototype.constructor
割按、People.constructor
等描述膨报。
ok,介紹完這三個(gè)屬性适荣,我們再來看看這三者有什么聯(lián)系现柠。
__proto__
、prototype
束凑、constructor
這三者到底是什么聯(lián)系
我們看看下面例子:
// 定義一個(gè)People構(gòu)造函數(shù)
function People () {
}
// 實(shí)例化一個(gè)person對象
const person = new People();
// 打印true --> 說明實(shí)例的__proto__與實(shí)例的構(gòu)造函數(shù)的prototype相等
console.log(person.__proto__ === People.prototype);
// 打印true --> 說明constructor是構(gòu)造函數(shù)的prototype里“自身”的一個(gè)屬性
console.log(People.prototype.hasOwnProperty('constructor'));
// 打印true --> 說明非頂級構(gòu)造函數(shù)的prototype.constructor指回這個(gè)構(gòu)造函數(shù)本身
console.log(People.prototype.constructor === People);
// 打印true --> 說明實(shí)例的__proto__.constructor 就是 構(gòu)造函數(shù)的prototype.constructor(由第一個(gè)打印可知person.__proto__ = People.prototype)
console.log(person.__proto__.constructor === People.prototype.constructor);
// 打印People --> 說明實(shí)例的constructor指向的就是實(shí)例的構(gòu)造函數(shù)
console.log(person.constructor.name);
// 打印fale --> 說明實(shí)例自身是沒有的constructor屬性的
console.log(person.hasOwnProperty('constructor'));
// 打印true, true --> 說明實(shí)例自身是沒有的constructor屬性的
// 它是繼承自實(shí)例的__proto__.constructor晒旅,即實(shí)例的構(gòu)造函數(shù)的prototype.constructor
console.log(person.constructor === person.__proto__.constructor, person.constructor === People.prototype.constructor);
解析:
-
__proto__
跟prototype
是什么聯(lián)系:
如果有一個(gè)實(shí)例,它是由一個(gè)構(gòu)造函數(shù)實(shí)例而來汪诉,那么這個(gè)實(shí)例的__proto__
一定指向這個(gè)構(gòu)造函數(shù)的prototype
废恋,即person.__proto__ = People.prototype
-
prototype
跟constructor
是什么聯(lián)系:
constructor
就是某個(gè)普通構(gòu)造函數(shù)的prototype
自身的一個(gè)屬性(用hasOwnProperty
可驗(yàn)證),它指向的就是這個(gè)構(gòu)造函數(shù)本身扒寄,即People.prototype.constructor = People
-
__proto__
跟constructor
是什么聯(lián)系:
__proto__
跟constructor
的聯(lián)系跟prototype
與constructor
的聯(lián)系一樣鱼鼓。因?yàn)橐?code>.__proto__結(jié)尾的,它最后一定指向某個(gè)構(gòu)造函數(shù)的原型對象(People.prototype
)该编,然后又由于constructor
是某個(gè)構(gòu)造函數(shù)的prototype
自身的一個(gè)屬性迄本,因此我們可以這么看:person.__proto__.constructor = People.prototype.constructor
ok,看到這里课竣,大家可以先暫停一下嘉赎,整理一下思路。理一理什么是__proto__
于樟、prototype
公条、constructor
;然后再理一理__proto__
迂曲、prototype
靶橱、constructor
這三者之間的聯(lián)系。然后接下來進(jìn)入最讓我們蒙圈的東西——原型鏈路捧。
什么是原型鏈
當(dāng)我們用構(gòu)造函數(shù)People
實(shí)例化了一個(gè)對象person
后关霸,訪問person
的方法或者屬性時(shí),會(huì)先在實(shí)例person
自身找有沒有對應(yīng)的方法屬性杰扫。有值的話队寇,則返回值,沒有的話則去person.__proto__
(People.prototype
)里找章姓;有值的話佳遣,則返回值炭序,沒有的話,又會(huì)去People.prototype.__proto__
(Object.prototype
)里找苍日。有值的話惭聂,則返回值;沒有的話相恃,又會(huì)去Object.prototype._proto__
里找辜纲,但是Object.prototype.__proto__
返回null
,原型鏈到頂拦耐,一條條原型鏈搜索完畢耕腾,都沒有,則返回undefined
杀糯。
在查找的過程中會(huì)遍歷以上的一條鏈扫俺,這條鏈就是原型鏈。上述的過程可以這么看(這個(gè)過程也是實(shí)現(xiàn)繼承的核心):
經(jīng)過上述的知識(shí)點(diǎn)固翰,相信大家對原型鏈應(yīng)該有個(gè)基本的認(rèn)識(shí)里吧狼纬,現(xiàn)在我們來總結(jié)一下,看看有沒有什么方法規(guī)律骂际。
方法總結(jié)
在看到一堆類似.__proto__.__proto__.__proto__
疗琉、.__proto__.__proto__.prototype
、.__proto__.prototype.consturtor
什么的歉铝,先不要慌盈简。
思想步驟:
- 我們直接看最后一個(gè)屬性,看看是以什么結(jié)尾
- 然后再一步步反推前面調(diào)用的都是什么對象
- 最后再推出它具體返回值的是什么
規(guī)律:
如果最后以
.__proto__
結(jié)尾太示,它最后返回的一定是某個(gè)構(gòu)造函數(shù)的prototype
(Object.prototype.__proto__
除外柠贤,它到頂了,是原型鏈的頂端类缤,返回null
)如果是以
.prototype
結(jié)尾臼勉,那么它前面一定是個(gè)構(gòu)造函數(shù),因?yàn)橹挥泻瘮?shù)才會(huì)有prototype
屬性(因?yàn)橐话阋?code>.prototype結(jié)尾返回的都是這個(gè)構(gòu)造函數(shù)的prototype
所有的方法與屬性呀非,所以題目很少會(huì)以.prototype
結(jié)尾)-
如果是以
.constructor
結(jié)尾坚俗,先弄清楚前面是什么- 如果前面是實(shí)例镜盯,那它直接返回創(chuàng)造實(shí)例的那個(gè)構(gòu)造函數(shù)岸裙;
- 如果前面直接是頂級基類構(gòu)造函數(shù)(
Function.constructor
)或者直接是普通構(gòu)造函數(shù)(People.constructor
),它會(huì)直接指向構(gòu)造所有函數(shù)的頂級基類構(gòu)造函數(shù)Function
(所有構(gòu)造函數(shù)都是函數(shù)速缆,都由頂級構(gòu)造函數(shù)Function
而來降允,所以constructor
當(dāng)然指向它; - 如果前面是非頂級構(gòu)造函數(shù)(普通函數(shù))的原型對象(
People.prototype.constructor
)艺糜,因?yàn)閷?shí)例的constructor
是繼承自普通構(gòu)造函數(shù).prototype.constructor
剧董,所以普通構(gòu)造函數(shù).prototype.constructor
必須指回它自己幢尚,(普通構(gòu)造函數(shù).prototype.constructor = 普通構(gòu)造函數(shù)
)。針對這點(diǎn)翅楼,我們看看它是怎么繼承來的尉剩。
constructor
整個(gè)繼承的流程是:在實(shí)例person
本身查找,找不到去person.__proto__
(People.prototype
)找毅臊,發(fā)現(xiàn)有People.prototype.constructor
理茎,并且People.prototype.constructor = People
返回它,所以person.constructor = People
管嬉。
流程如圖所示:[圖片上傳失敗...(image-eff596-1674636118495)]
ok皂林,經(jīng)過上面總結(jié)出的思想步驟跟規(guī)律,我們來試試:
// 定義一個(gè)People構(gòu)造函數(shù)
function People() {
}
// 實(shí)例化一個(gè)person對象
const person = new People();
// 第一題
console.log(People.__proto__);
// 第二題
console.log(People.constructor);
// 第三題
console.log(person.prototype);
// 第四題
console.log(person.__proto__.__proto__);
// 第五題
console.log(People.__proto__.prototype);
// 第六題
console.log(person.__proto__.__proto__.constructor);
// 第七題
console.log(Object.__proto__);
-
我們以第一道題為例蚯撩,解析一下:
- 先看是以什么結(jié)尾础倍。以
.__proto__
- ok,心里有個(gè)大概了胎挎,根據(jù)規(guī)律總結(jié)第一點(diǎn)沟启,它肯定返回某個(gè)構(gòu)造函數(shù)的
prototype
- 再反推一下前面調(diào)用的都是什么對象。前面是
People
犹菇,People
是什么美浦?是構(gòu)造函數(shù),函數(shù)都有一個(gè)頂級基類構(gòu)造函數(shù)项栏,那就是Function
浦辨,所以People.__proto__
返回的就是Function.prototype
。
- 先看是以什么結(jié)尾础倍。以
-
我們以第二道題為例沼沈,解析一下:
- 先看是以什么結(jié)尾流酬。以
.constructor
- 調(diào)用對象直接是普通構(gòu)造函數(shù),根據(jù)規(guī)律總結(jié)第三點(diǎn)的第二小點(diǎn)列另,直接得出
Function
- 先看是以什么結(jié)尾流酬。以
-
我們再以第六道題為例芽腾,解析一下:
- 先看是以什么結(jié)尾。以
.constructor
- 再反推一下前面是調(diào)用的都是什么對象页衙。先看
person.__proto__
摊滔,返回的是People.prototype
,那這題就變成了People.prototype.__proto__.constructor
店乐。再繼續(xù)看艰躺,People.prototype.__proto__
返回的是什么,Object.prototype
眨八,那這題實(shí)際就是Object.prototype.constructor
腺兴。根據(jù)規(guī)律總結(jié)第三點(diǎn)的第三小點(diǎn),那它返回的就是Object
本身廉侧。
- 先看是以什么結(jié)尾。以
大家一定要注意页响,
Object.__proto__
跟Function.__proto__
篓足,Object
跟Function
都是頂級構(gòu)造函數(shù),所以Object.__proto__
闰蚕、Function.__proto__
返回的都是Function.prototype
牛刀小試
根據(jù)上面對__proto__
栈拖、prototype
、constructor
的特點(diǎn)總結(jié)没陡,還有方法總結(jié)辱魁,我們可以拿下面這道題來試試,如果大家都可以正確無誤的答出來诗鸭,那大家對原型應(yīng)該就了解的差不多了
function Person(name) {
this.name = name
}
var p2 = new Person('king');
console.log(p2.__proto__); // Person.prototype
console.log(p2.__proto__.__proto__); // Object.prototype
console.log(p2.__proto__.__proto__.__proto__); // null
console.log(p2.__proto__.__proto__.__proto__.__proto__); // 報(bào)錯(cuò)
console.log(p2.constructor); // Person
console.log(p2.prototype); // undefined
console.log(Person.constructor); // Function
console.log(Person.prototype); // 輸出Person.prototype這個(gè)對象里所有的方法和屬性
console.log(Person.prototype.constructor); // Person
console.log(Person.prototype.__proto__); // Obejct.prototype
console.log(Person.__proto__); // Fuction.prototype
console.log(Function.prototype.__proto__); // Obeject.prototype
console.log(Function.__proto__); // Function.prototype
console.log(Object.__proto__); // Function.prototype
console.log(Object.prototype.__proto__); // null
最后
原型染簇、原型鏈本來就挺繞的,所以大家先了解__proto__
强岸、prototype
吐葵、constructor
是什么踊兜,再明白它們之間的是什么聯(lián)系忧便,循環(huán)漸進(jìn)牺汤。等理解以后,多畫幾遍原型鏈圖加深理解妓盲。OK杂拨,最后祭出一張?jiān)玩湀D:
紅色鏈表示的就是實(shí)例
person
原型鏈
寫著寫著,發(fā)現(xiàn)又寫了一大堆悯衬,希望能夠幫助到大家弹沽。如果覺得覺得寫得好的,有幫助到的筋粗,歡迎大家點(diǎn)贊策橘,也歡迎大家評論交流。
既然明白了什么是原型鏈娜亿,那還不趕緊趁熱打鐵丽已,進(jìn)階看看什么是JS繼承吧!