JavaScript 之原型鏈
前言
在 JavaScript 中 一直感覺原型鏈是個很繞口或者是個理解起來很費勁的東西 那麼 希望通過這篇文章能夠對你有所幫助 既然說到原型鏈 那麼第一個問題就是 建立物件 畢竟 如果沒有物件 哪裡來的原型鏈呢 是不是 所以 先來看看建立物件的幾種方法吧
建立物件的幾種方法
- 通過字面常量的方式建立 也可能是我們日常開發中使用的最多的方法
var obj1 = { name: 'obj1' }
複製程式碼
- 通過
new Object
其實這種建立方式等價於字面常量建立
var obj2 = new Object({
name: 'obj2'
})
複製程式碼
- 通過建構函式建立
var foo = function(name) {
this.name = name
}
var obj3 = new foo('obj3')
複製程式碼
- 通過
Object.create
建立
var n = { name: 'obj4' }
var obj4 = Object.create(n)
複製程式碼
原型鏈之中的關係梳理
在開始下面的長篇大論以前 還是先來看一張圖 這張圖其實非常好的說明了例項 原型物件 建構函式
之間的關聯關係 其實要是可以看明白這張圖 也基本明白啥是原型鏈以及原型鏈的工作原理了
-
建構函式和例項: 例如上文的
obj3
他就是建構函式foo
new 出來的一個例項 這裡多嘴一句 但凡任意函式名前面 有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)
複製程式碼
我們可以開啟瀏覽器看看控制檯都輸出了些什麼
這樣也算是有圖有真相了吧 233333
- 原型物件和建構函式: 看到這裡 肯定會有人想問 那既然我們寫一個函式 就會有一個 prototype 那麼這麼多的 prototype 又如何區分呢 這裡 就要用到
constructor
也就是說 不論這個函式怎麼寫 原型物件都會通過constructor
來找到它 老規矩 還是上程式碼
var foo = function(name) {
this.name = name
}
console.log(foo.prototype.constructor === foo)
複製程式碼
控制檯的列印結果如下
所以這個觀點也是被程式碼所佐證了
- 例項和原型物件: 例項和原型物件之間 是通過
__proto__
相關聯 這裡也有一點同樣值得注意 函式同樣擁有__proto__
因為函式既是函式 也同樣是例項 他的原型物件就是一個Function
同樣的 還是來上程式碼 並觀察輸出結果
var foo = function(name) {
this.name = name
}
var obj3 = new foo('obj3')
複製程式碼
寫到這裡 其實就已經差不多了 有了理論支援 那麼接下去 其實就是得出結論啦
原型鏈的工作原理
既然已經知曉了例項 建構函式 原型物件之間的關係 那麼 其實當查詢一個例項是否擁有一個屬性的時候 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')
複製程式碼
控制檯輸出結果如下
instanceof
instanceof
這個應該大家都不陌生 主要還是用於區分某個例項是否是屬於一個特定的類 下面來說說instanceof
的工作原理
其實也非常簡單 上文其實有提到過 例項是通過__proto
來和原型物件關聯的 而建構函式呢 又是通過prototype
來關聯原型物件的 所以呢 如果一個例項的__proto__
和一個建構函式的prototype
如果是一致的 那麼其實是可以暫且說 這個例項是屬於某個特定的類的 為什麼說是暫且呢 一會兒來分析 先來看看剛才說的這些對不對吧 首先通過建構函式 new 一個 obj 例項 然後在控制檯裡看看
剛才說的對不對
var foo = function(name) {
this.name = name
}
var obj = new foo('obj1')
複製程式碼
看看控制檯輸出了啥
和我們之前說的一樣 建構函式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
複製程式碼
所以這也是為什麼說 用instanceof 有時候並不夠嚴謹的原因 那麼真正嚴謹的應該是什麼呢 其實答案應該也是呼之欲出了 上文有提到過 每一個建構函式在書寫的時候 JavaScript模板引擎就會自動新增一個prototype
而這個prototype
又是通過constructor
去判斷到底具體是哪個建構函式 所以 用constructor
其實應該是更為嚴謹的一種寫法
obj1.__proto__.constructor === foo // true
obj1.__proto__.constructor === Object // false
複製程式碼