什么是原型鏈?
我們先看一下下面的這個小例子:
let data = {
name:'merit'
}
alert(data)
這時候,彈出的窗口里面顯示著 [object Object]糜工,相信大家應(yīng)該也都見過這個弊添,可是為什么會是這個呢?
其實捌木,再用alert顯示對象的時候油坝,會默認(rèn)調(diào)用對象的toString函數(shù),可是,我們明明沒有給data設(shè)置這個函數(shù)啊澈圈,那這個函數(shù)又是哪里來的呢彬檀,這時候我們就把整個data給打印一下看一看:
我們看到,data不止有我們設(shè)置的name屬性瞬女,還有一個我們沒設(shè)置的__proto__屬性窍帝,那這屬性里面又是些什么東西呢,讓我們再打開看一下:
這時候我們就在里賣弄看到了我們用到的toString函數(shù)诽偷。
上面這個是直接創(chuàng)建了一個Object實例對象坤学,下面讓我們用構(gòu)造函數(shù)創(chuàng)建一個對象,看看是什么效果:
function MyObj(name){
this.name = name
}
MyObj.prototype.myToString = function(){
console.log(this.name)
}
let bar = new MyObj('merit')
alert(bar)
結(jié)果顯然是和上面的一樣為 [object Object]报慕,我們來看一看這個的toString方法是怎么得到的:
我們可以看到拥峦,bar這個對象本身有一個__proto__屬性,這個屬性里面竟然還有我們通過MyObj.prototype.myToString定義的函數(shù)卖子,這個我們一會兒再說略号。然后在這個__proto__屬性中,還有一個__proto__屬性洋闽,里面有著我們所需要的toString函數(shù)玄柠。
當(dāng)我們調(diào)用對象的某個屬性的時候,就會先去看它本身有沒有這個屬性诫舅,沒有的話羽利,就會去看他的__proto__中有沒有這個屬性,有的話就調(diào)用刊懈,沒有的話就繼續(xù)看這個__proto__中的__proto__里面有沒有这弧,只到最后一層,如果都沒有的話虚汛,才會報錯匾浪。
上面這個例子中,如果把myToString函數(shù)名換成toString卷哩,那么alert(bar)再調(diào)用bar.toString就是我們自己寫的內(nèi)容了蛋辈。
這種通過__proto__屬性一層層的連接形成了一個鏈的樣子就叫做原型鏈。
prototype與_proto_的關(guān)系
當(dāng)我們創(chuàng)建一個對象的實例的時候将谊,實例本身就會自帶一個__proto__屬性冷溶,該屬性指向其構(gòu)造函數(shù)的prototype屬性,而所有構(gòu)造函數(shù)的prototype屬性都是一個Object的一個實例尊浓。而這個實例就會有一個指向Object.prototype的_proto__逞频,所有對象類型無論是Array,String還是Function,最終的__proto__指向都是這個位置栋齿。
明白了原型鏈苗胀,其實就能夠理解很多的東西了托酸,比如我們經(jīng)常使用的instanceof判斷類型和實現(xiàn)繼承的一些方式。
instanceof
我們經(jīng)常使用instanceof去判斷一個數(shù)據(jù)的數(shù)據(jù)類型柒巫,其實A instanceof B的本質(zhì)就是去判斷A這個對象的原型鏈上是否有B.prototype励堡。這樣你就能很好的明白下面的一些結(jié)果了:
let bar = 'merit'
let bar_s = new String('merit')
let bar_a = [1,2,3]
let bar_f = function(){console.log(1)}
bar instanceof Object //false
bar_s instanceof String //true
bar_a instanceof Array//true
bar_f instanceof Function//true
//任何對象類型的原型鏈終點都指向到Object的prototype
String instanceof Object//true
Array instanceof Object//true
Function instanceof Object //true
Object instanceof Object //true
Object instanceof Function //true
String instanceof Function//true
Array instanceof Function //true
Function instanceof Function //true
//對于最后一組,我們要知道{},[]創(chuàng)建對象或數(shù)組都是new Object()/new Array()的語法糖堡掏,而Object,Array本身這些構(gòu)造器应结,又都是Function的一個實例。
既然對instanceof的原理理解了泉唁,我們也可以簡單的寫一個簡易版的instanceof
//myInstanceof
function myInstanceof(A,B){
while(A.__proto__){
if(A.__proto__===B.prototype){
return true
}
A=A.__proto__
}
return false
}
繼承
通過對原型鏈的理解鹅龄,我們就可以很好的實現(xiàn)繼承了。
function Parent(value){
this.tag='我是父對象'
this.val = value
}
Parent.prototype.showVal = function(){console.log(this.val)}
組合繼承
function Child(value,name){
Parent.apply(this,value) //通過這一步可以保證子對象里面有父對象的值
this.tag = '我是子對象' //會覆蓋掉父對象的賦值
this.name = name;
}
//接下來我們要保證子對象能調(diào)用父對象的方法
Child.prototype = new parent()
Child.prototype.constructor = Child
let bar = new Child('111','merit')
雖然我不知道為什么這個名字叫做組合對象亭畜,但是原理還是很明顯的扮休,就是讓子對象的prototype指向父對象的一個實例,然后利用這個實例的__proto__去調(diào)用父對象的方法拴鸵,下面一張圖應(yīng)該能很詳細(xì)的說明這一點
通過上面這個圖玷坠,我們可以看出子對象的一個實例bar是如何調(diào)用父對象的方法的,但是我們也能夠看出來一個問題劲藐,就是通過這種構(gòu)建一個父對象的實例在中間進(jìn)行過渡的方式八堡,會造成內(nèi)存的浪費,因為我們并不會使用到父對象里面的值聘芜,這時候我們就可以通過下面的繼承方式來改善
寄生組合繼承
function Child(value,name){
Parent.call(this,value)
this.tag='子對象'
this.name = name
}
//這個會創(chuàng)造一個__proto__指向Parent.prototype的空對象
let childPrototype = Object.creat(Parent.prototype)
childPrototype.constructor = Child
Child.prototype = childPrototype
通過創(chuàng)建父對象實例而是通過Object創(chuàng)建空對象的方式繼承兄渺,就很好的解決了上面的問題,可以說是一種比較完美的繼承方案了汰现。