什么是原型鏈?
我們先看一下下面的這個小例子:
let data = {
name:'merit'
}
alert(data)
這時候,彈出的窗口里面顯示著 [object Object],相信大家應該也都見過這個,可是為什么會是這個呢?
其實,再用alert顯示對象的時候,會默認調用對象的toString函數,可是,我們明明沒有給data設置這個函數啊,那這個函數又是哪里來的呢,這時候我們就把整個data給打印一下看一看:

我們看到,data不止有我們設置的name屬性,還有一個我們沒設置的__proto__屬性,那這屬性里面又是些什么東西呢,讓我們再打開看一下:

這時候我們就在里賣弄看到了我們用到的toString函數。
上面這個是直接創(chuàng)建了一個Object實例對象,下面讓我們用構造函數創(chuàng)建一個對象,看看是什么效果:
function MyObj(name){
this.name = name
}
MyObj.prototype.myToString = function(){
console.log(this.name)
}
let bar = new MyObj('merit')
alert(bar)
結果顯然是和上面的一樣為 [object Object],我們來看一看這個的toString方法是怎么得到的:

我們可以看到,bar這個對象本身有一個__proto__屬性,這個屬性里面竟然還有我們通過MyObj.prototype.myToString定義的函數,這個我們一會兒再說。然后在這個__proto__屬性中,還有一個__proto__屬性,里面有著我們所需要的toString函數。
當我們調用對象的某個屬性的時候,就會先去看它本身有沒有這個屬性,沒有的話,就會去看他的__proto__中有沒有這個屬性,有的話就調用,沒有的話就繼續(xù)看這個__proto__中的__proto__里面有沒有,只到最后一層,如果都沒有的話,才會報錯。
上面這個例子中,如果把myToString函數名換成toString,那么alert(bar)再調用bar.toString就是我們自己寫的內容了。
這種通過__proto__屬性一層層的連接形成了一個鏈的樣子就叫做原型鏈。
prototype與_proto_的關系
當我們創(chuàng)建一個對象的實例的時候,實例本身就會自帶一個__proto__屬性,該屬性指向其構造函數的prototype屬性,而所有構造函數的prototype屬性都是一個Object的一個實例。而這個實例就會有一個指向Object.prototype的_proto__,所有對象類型無論是Array,String還是Function,最終的__proto__指向都是這個位置。
明白了原型鏈,其實就能夠理解很多的東西了,比如我們經常使用的instanceof判斷類型和實現繼承的一些方式。
instanceof
我們經常使用instanceof去判斷一個數據的數據類型,其實A instanceof B的本質就是去判斷A這個對象的原型鏈上是否有B.prototype。這樣你就能很好的明白下面的一些結果了:
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)建對象或數組都是new Object()/new Array()的語法糖,而Object,Array本身這些構造器,又都是Function的一個實例。
既然對instanceof的原理理解了,我們也可以簡單的寫一個簡易版的instanceof
//myInstanceof
function myInstanceof(A,B){
while(A.__proto__){
if(A.__proto__===B.prototype){
return true
}
A=A.__proto__
}
return false
}
繼承
通過對原型鏈的理解,我們就可以很好的實現繼承了。
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;
}
//接下來我們要保證子對象能調用父對象的方法
Child.prototype = new parent()
Child.prototype.constructor = Child
let bar = new Child('111','merit')
雖然我不知道為什么這個名字叫做組合對象,但是原理還是很明顯的,就是讓子對象的prototype指向父對象的一個實例,然后利用這個實例的__proto__去調用父對象的方法,下面一張圖應該能很詳細的說明這一點

通過上面這個圖,我們可以看出子對象的一個實例bar是如何調用父對象的方法的,但是我們也能夠看出來一個問題,就是通過這種構建一個父對象的實例在中間進行過渡的方式,會造成內存的浪費,因為我們并不會使用到父對象里面的值,這時候我們就可以通過下面的繼承方式來改善
寄生組合繼承
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)建空對象的方式繼承,就很好的解決了上面的問題,可以說是一種比較完美的繼承方案了。