JavaScript之原型鏈

皮蛋和豆樹發表於2019-06-26

JavaScript 之原型鏈

前言

在 JavaScript 中 一直感覺原型鏈是個很繞口或者是個理解起來很費勁的東西 那麼 希望通過這篇文章能夠對你有所幫助 既然說到原型鏈 那麼第一個問題就是 建立物件 畢竟 如果沒有物件 哪裡來的原型鏈呢 是不是 所以 先來看看建立物件的幾種方法吧

建立物件的幾種方法

  1. 通過字面常量的方式建立 也可能是我們日常開發中使用的最多的方法
var obj1 = { name: 'obj1' }
複製程式碼
  1. 通過new Object 其實這種建立方式等價於字面常量建立
var obj2 = new Object({
  name: 'obj2'
})
複製程式碼
  1. 通過建構函式建立
var foo = function(name) {
  this.name = name
}
var obj3 = new foo('obj3')
複製程式碼
  1. 通過Object.create建立
var n = { name: 'obj4' }
var obj4 = Object.create(n)
複製程式碼

原型鏈之中的關係梳理

在開始下面的長篇大論以前 還是先來看一張圖 這張圖其實非常好的說明了例項 原型物件 建構函式之間的關聯關係 其實要是可以看明白這張圖 也基本明白啥是原型鏈以及原型鏈的工作原理了

關係圖

  • 建構函式和例項: 例如上文的obj3 他就是建構函式foonew 出來的一個例項 這裡多嘴一句 但凡任意函式名前面 有new這個關鍵字 那麼這個函式都可以被稱之為建構函式 由於這個點非常簡單 所以我們就一筆帶過吧 相信有 JS 基礎的同學 應該都很容易理解

  • 建構函式和原型物件: 當我們在寫一個函式的時候 不論這個函式的函式體是什麼 哪怕一行程式碼也沒有 他就是空有一個function JavaScript 的模板引擎也會為這個函式體自動新增一個 prototype 也就是原型物件 也就是圖上所說的 建構函式和原型物件通過 prototype 關聯 這個地方還有一點非常重要 只有函式才有 prototype 而例項是沒有的 這一點可以用程式碼來佐證

var obj1 = { name: 'obj1' }
console.log('obj1的prototype:' + obj1.prototype)

var foo = function(name) {
  this.name = name
}
console.log('foo的prototype:' + foo.prototype)
複製程式碼

我們可以開啟瀏覽器看看控制檯都輸出了些什麼

JavaScript之原型鏈

這樣也算是有圖有真相了吧 233333

  • 原型物件和建構函式: 看到這裡 肯定會有人想問 那既然我們寫一個函式 就會有一個 prototype 那麼這麼多的 prototype 又如何區分呢 這裡 就要用到constructor 也就是說 不論這個函式怎麼寫 原型物件都會通過constructor來找到它 老規矩 還是上程式碼
var foo = function(name) {
  this.name = name
}
console.log(foo.prototype.constructor === foo)
複製程式碼

控制檯的列印結果如下

JavaScript之原型鏈

所以這個觀點也是被程式碼所佐證了

  • 例項和原型物件: 例項和原型物件之間 是通過__proto__相關聯 這裡也有一點同樣值得注意 函式同樣擁有__proto__ 因為函式既是函式 也同樣是例項 他的原型物件就是一個Function 同樣的 還是來上程式碼 並觀察輸出結果
var foo = function(name) {
  this.name = name
}
var obj3 = new foo('obj3')
複製程式碼

JavaScript之原型鏈

寫到這裡 其實就已經差不多了 有了理論支援 那麼接下去 其實就是得出結論啦

原型鏈的工作原理

既然已經知曉了例項 建構函式 原型物件之間的關係 那麼 其實當查詢一個例項是否擁有一個屬性的時候 JS 的模板引擎會逐級向上尋找 也就是

例項 -> 建構函式 -> 原型物件 -> 原型物件的原型物件 -> ... -> Object.prototype

如果這一整個查詢過程都沒有找到對應的屬性那麼則會返回undefined 如果有則返回對應的屬性值 既然也知曉了工作原理 其實就可以在日常工作中通過原型鏈 去幫助我們寫出更加簡潔 更加優雅的程式碼

原型鏈的實際應用

假設我們現在通過相同的建構函式建立了多個例項 而這些例項有時候都會呼叫相同函式 有些同學可能會每一次都會拷貝一份方法 其實大可不必 我們可以直接將相同程式碼直接掛載到prototype上即可 程式碼如下

var foo = function(name) {
  this.name = name
}
foo.prototype.sayName = function(name) {
  console.log('我是物件' + this.name)
}
var obj3 = new foo('obj3')
obj3.sayName('obj3')
var obj5 = new foo('obj5')
obj5.sayName('obj5')
複製程式碼

控制檯輸出結果如下

JavaScript之原型鏈

instanceof

instanceof 這個應該大家都不陌生 主要還是用於區分某個例項是否是屬於一個特定的類 下面來說說instanceof的工作原理

其實也非常簡單 上文其實有提到過 例項是通過__proto來和原型物件關聯的 而建構函式呢 又是通過prototype來關聯原型物件的 所以呢 如果一個例項的__proto__和一個建構函式的prototype如果是一致的 那麼其實是可以暫且說 這個例項是屬於某個特定的類的 為什麼說是暫且呢 一會兒來分析 先來看看剛才說的這些對不對吧 首先通過建構函式 new 一個 obj 例項 然後在控制檯裡看看 剛才說的對不對

var foo = function(name) {
  this.name = name
}
var obj = new foo('obj1')
複製程式碼

看看控制檯輸出了啥

JavaScript之原型鏈

和我們之前說的一樣 建構函式foo的prototype和例項obj的__proto__屬性 是一致的 那麼接下來來看一個神奇的問題 obj instanceof Object會返回什麼? 答案是 true如果有不相信的同學 其實可以自己去控制檯裡試試看 一定是返回的true 這是為什麼呢 其實也還是脫離不了原型鏈這個概念

首先 例項obj是建構函式foo new出來的一個物件 但是呢 建構函式foo 其實也是Object下的一個物件 這麼一說可能還有點抽象 但是如果結合 instanceof的工作原理的話 其實就不難理解了 其實無外乎也就是一個原型鏈的查詢過程

// 首先
    obj1.__proto__ === foo.prototype // true
// 其次
    foo.prototype.__poto__ === Object.prototype // true
// 最後
    obj1 instanceof Object // true
複製程式碼

JavaScript之原型鏈

所以這也是為什麼說 用instanceof 有時候並不夠嚴謹的原因 那麼真正嚴謹的應該是什麼呢 其實答案應該也是呼之欲出了 上文有提到過 每一個建構函式在書寫的時候 JavaScript模板引擎就會自動新增一個prototype 而這個prototype又是通過constructor去判斷到底具體是哪個建構函式 所以 用constructor其實應該是更為嚴謹的一種寫法

obj1.__proto__.constructor === foo // true
obj1.__proto__.constructor === Object // false
複製程式碼

All is well ?

相關文章