js 原型鏈

什么是原型鏈?

我們先看一下下面的這個小例子:

let data = {
    name:'merit'
}
alert(data)

這時候,彈出的窗口里面顯示著 [object Object],相信大家應該也都見過這個,可是為什么會是這個呢?

其實,再用alert顯示對象的時候,會默認調用對象的toString函數,可是,我們明明沒有給data設置這個函數啊,那這個函數又是哪里來的呢,這時候我們就把整個data給打印一下看一看:

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

1593426483110.png

這時候我們就在里賣弄看到了我們用到的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)建空對象的方式繼承,就很好的解決了上面的問題,可以說是一種比較完美的繼承方案了。

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
【社區(qū)內容提示】社區(qū)部分內容疑似由AI輔助生成,瀏覽時請結合常識與多方信息審慎甄別。
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發(fā)布,文章內容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

友情鏈接更多精彩內容