四張圖帶你搞定原型和原型鏈

在講原型和原型鏈之前,先鋪墊一些前置知識(shí):

「所有的對(duì)象都是通過(guò)
new 函數(shù)生成的?!?/strong> 包括let obj = {},這種形式其實(shí)是語(yǔ)法糖,本質(zhì)上是通過(guò)let obj = new Object()生成的。那么函數(shù)又是如何生成的呢?從圖中可以清晰的看出函數(shù)本質(zhì)上是通過(guò)new Function生成的,盡管我們平時(shí)不會(huì)這么去寫,當(dāng)然也不建議這么去寫function Test(){}
//相當(dāng)于
let Test = new Function();那么
Function函數(shù)又是誰(shuí)生成的呢?Function函數(shù)也是函數(shù),剛剛我們說(shuō)函數(shù)是通過(guò)new Function生成,但它是一種特殊的情況,不通過(guò)任何東西創(chuàng)建,它是JS引擎啟動(dòng)的時(shí)候直接添加到內(nèi)存當(dāng)中的。「所有的函數(shù)也都是對(duì)象,既然是對(duì)象,那么函數(shù)一定會(huì)有屬性?!?/strong> 比如:
Array.form、Array.isArray等等「對(duì)象是一種引用類型。」
?? 原型 prototype
所有的函數(shù)都有一個(gè)屬性:prototype,稱之為函數(shù)原型。函數(shù)創(chuàng)建之初就會(huì)自動(dòng)加上prototype屬性。
那什么是原型呢?
默認(rèn)情況下,prototype就是一個(gè)普通的object對(duì)象。

默認(rèn)情況下,prototype中有一個(gè)屬性:constructor,它也是一個(gè)對(duì)象,它指向構(gòu)造函數(shù)本身。

這張圖很清晰說(shuō)明了prototype和constructor之間的關(guān)系,每個(gè)函數(shù)(add、Object、Array、nothing)都有一個(gè)屬性prototype,它指向函數(shù)的原型,而函數(shù)的原型中也有一個(gè)屬性constructor,它也是一個(gè)對(duì)象,constructor指向構(gòu)造函數(shù)本身。
那原型有什么用呢?原型本身沒(méi)什么用,但是配合隱式原型卻大有作為
?? 隱式原型 __proto__
「所有的對(duì)象都有一個(gè)屬性:__proto__,稱之為隱式原型?!?/strong> 前后兩個(gè)下劃線表示系統(tǒng)私有屬性,不要輕易動(dòng)它。
默認(rèn)情況下,隱式原型指向創(chuàng)建該對(duì)象的函數(shù)的原型。這句話特別重要,它將隱式原型跟原型聯(lián)系起來(lái)了,那什么意思呢?
舉個(gè)栗子??:
function Test(){
}
let obj = new Test();
// obj.__proto__ === Test.prototype; 返回true
舉個(gè)栗子??:
function Test(){
return {};// 這里{}是語(yǔ)法糖,本質(zhì)上是通過(guò)new Object()創(chuàng)建的
}
let obj = new Test();//由于Test函數(shù)中返回 {},所以new Test()的結(jié)果是 let obj = new Object()
// obj.__proto__ === Object.prototype; 返回true
現(xiàn)在我們知道隱式原型指向誰(shuí),然后我們將prototype、constructor、__proto__三者關(guān)系繪圖如下:

在前置知識(shí)中,已經(jīng)說(shuō)過(guò)所有的對(duì)象都是通過(guò)new 函數(shù)進(jìn)行創(chuàng)建的,便有了上圖關(guān)系。細(xì)心的小伙伴應(yīng)該已經(jīng)發(fā)現(xiàn)對(duì)象1的__proto__、對(duì)象2的__proto__以及函數(shù)add的prototype三者指向同一塊內(nèi)存空間,這也就解釋了為什么要把函數(shù)寫在原型上,這是因?yàn)閷⒑瘮?shù)寫在原型上,只要是通過(guò)add構(gòu)造函數(shù)創(chuàng)建的對(duì)象都可以訪問(wèn)這個(gè)函數(shù)。
function Add(name,age){
this.name = name;
this.age = age;
}
Add.prototype.say = function(){
console.log("法醫(yī)",this.name,this.age)
}
let obj1 = new Add("前端獵手",18);
let obj2 = new Add("仵作",20);
obj1.say(); //法醫(yī) 前端獵手 18
obj2.say(); //法醫(yī) 仵作 20
訪問(wèn)對(duì)象成員的順序是:首先會(huì)看當(dāng)前對(duì)象中是否存在該屬性或者方法,若存在,就直接使用了,否則繼續(xù)順著原型鏈依次查找。

「MDN文檔中一段話:」
鏈接查看
之所以會(huì)繼承Array.prototype就是因?yàn)?code style="overflow-wrap: break-word;padding: 2px 4px;border-radius: 4px;margin-right: 2px;margin-left: 2px;font-family: "Operator Mono", Consolas, Monaco, Menlo, monospace;word-break: break-all;color: rgb(145, 109, 213);font-weight: bolder;background-image: none;background-position: initial;background-size: initial;background-repeat: initial;background-attachment: initial;background-origin: initial;background-clip: initial;">隱式原型的存在,這也是提示我們,將來(lái)要把對(duì)象需要共享的東西寫在原型上,特別是函數(shù),這種行為有個(gè)比較有意思的名稱,叫猴子補(bǔ)丁,也許是因?yàn)??善于模仿,在原型中加入成員,以增強(qiáng)對(duì)象的功能,但也是有弊端的,會(huì)造成原型污染,所以還是謹(jǐn)慎使用。
?? 原型鏈
這張圖搞清楚后,自然明白何為原型鏈,我們一起過(guò)一遍

我們先看
白色線條,白色線條表示原型,在原型部分我們已經(jīng)說(shuō)了,所有的函數(shù)都有一個(gè)屬性prototype,那么Object函數(shù)的原型指向Object原型,同理,我們自定義函數(shù)的原型必然指向自定義函數(shù)原型,這里有個(gè)比較特殊的點(diǎn),就是Function函數(shù),沒(méi)有任何東西創(chuàng)建它,它是由JS引擎啟動(dòng)的時(shí)候直接添加到內(nèi)存里面的,故Function函數(shù)直接指向Function原型。再看
綠色線條,綠色線條表示new,讀到這里,想必大家都知道所有的函數(shù)都是通過(guò)new Function()創(chuàng)建的,所以Function函數(shù)分別指向Object和自定義函數(shù)無(wú)可厚非,圖中有一條線是自定義函數(shù)指向自定義對(duì)象,文章開(kāi)頭已經(jīng)說(shuō)了,所有的對(duì)象都是通過(guò)new 函數(shù)進(jìn)行創(chuàng)建的,代碼表示:function Test(){
}
let obj = new Test();//這里的 obj 可以表示圖中的自定義對(duì)象最后再看
藍(lán)色線條,藍(lán)色線條表示隱式原型,我在隱式原型那部分也已經(jīng)說(shuō)了,所有的對(duì)象都有一條屬性__proto__,那函數(shù)是對(duì)象吧,那隱式原型指向誰(shuí)呢?隱式原型指向創(chuàng)建該對(duì)象的函數(shù)的原型,要理解這句話,我們必須要知道函數(shù)是誰(shuí)創(chuàng)建的?這個(gè)誰(shuí)的原型是什么?所有的函數(shù)都是Function函數(shù)創(chuàng)建的,Function函數(shù)的原型指向Function原型,這是個(gè)特殊點(diǎn),故Object函數(shù)和自定義函數(shù)的隱式原型指向Function原型,Function函數(shù)也是函數(shù),由于沒(méi)有誰(shuí)創(chuàng)建它,是被直接添加到內(nèi)存的,它的原型是指向Function原型,這同樣是一個(gè)特殊點(diǎn)。function Test(){}
//相當(dāng)于
let Test = new Function();
Test.__proto__ === Function.prototype; // true我們都知道所有的函數(shù)都有共同的成員,比如
call、apply、bind等等,我們并沒(méi)給自定義函數(shù)上加上這些成員,那么為什么可以使用呢?這是因?yàn)樗械暮瘮?shù)的隱式原型指向Function的原型,這些方法都存在于Function的原型上,所以每個(gè)函數(shù)都可以使用這些成員,這就是繼承的效果。繼續(xù)來(lái)看,自定義函數(shù)可以通過(guò)new創(chuàng)建自定義對(duì)象,自定義對(duì)象也是對(duì)象,那必然有隱式原型
__proto__,指向創(chuàng)建該對(duì)象的函數(shù)原型,所以自定義對(duì)象的隱式原型指向自定義函數(shù)原型,那么自定義函數(shù)原型又指向誰(shuí)呢?不知道大家是否還記得我在原型那部分說(shuō)過(guò)一句話:默認(rèn)情況下,prototype是一個(gè)普通的Object對(duì)象,所以可以認(rèn)為prototype是通過(guò)new Object()創(chuàng)建的,所以prototype是個(gè)對(duì)象,故自定義函數(shù)的prototype的隱式原型指向Object的原型,看代碼:function test(){};//自定義函數(shù)
test.prototype.__proto__ === Object.prototype;// true
?? 特殊點(diǎn):Object的原型的隱式原型指向
null,Object.prototype.__proto__ === null,返回true現(xiàn)在知道什么是原型鏈了吧,
自定義對(duì)象的隱式原型指向自定義函數(shù)的原型,自定義函數(shù)的原型的隱式原型又指向Object原型,Object原型又指向null,這種鏈?zhǔn)降年P(guān)系就是原型鏈
自測(cè)題一道:大家可以試著做一下,然后可以根據(jù)最后一張圖進(jìn)行檢查
function Fayi() {}
Fayi.prototype.camel = function() {}
var u1 = new Fayi();
var u2 = new Fayi();
console.log(u1.camel === u2.camel);
console.log(Fayi.prototype.constructor);
console.log(Fayi.prototype === Function.prototype);
console.log(Fayi.__proto__ === Function.prototype);
console.log(Fayi.__proto__ === Function.__proto__);
console.log(u1.__proto__ === u2.__proto__);
console.log(u1.__proto__ === Fayi.__proto__);
console.log(Function.__proto__ === Object.__proto__);
console.log(Function.prototype.__proto__ === Object.prototype.__proto__);
console.log(Function.prototype.__proto__ === Object.prototype);
?? 好了, 以上就是我的分享,小伙伴們點(diǎn)個(gè)贊再走吧 ?? 支持一下哦~ ??,我會(huì)更有動(dòng)力的 ??
