在 JavaScript 中,我們通常用 typeof
判斷型別,但是在判斷引用型別的值時,常常會遇到一個問題:無論引用的是什麼型別的物件,都會返回 "object"(當然還有 "function") 。有時候我們需要知道這個引用物件的型別是陣列還是一個包裝物件,這個時候 instanceof
就可以派上用場了。
廢話不多說,先來幾個例子熱身一下,全部都知道同學,請點選右上角的關閉按鈕;模模糊糊的同學,可以繼續閱讀,只要掌握了原理,這些題目真的是易如反掌。
const a = 'abc';
console.log(a instanceof String); // ?
const b = new String('abc');
console.log(b instanceof String); // ?
複製程式碼
console.log(String instanceof String); // ?
console.log(Object instanceof Object); // ?
console.log(Function instanceof Function); // ?
console.log(Function instanceof Object); // ?
複製程式碼
一、instanceof 是如何工作的
在 MDN 上是這樣描述 instanceof
的:
instanceof
運算子用於測試建構函式的prototype
屬性是否出現在物件原型鏈中的任何位置
換句話說,如果A instanceof B
,那麼 A
必須是一個物件,而 B
必須是一個合法的 JavaScript 函式。在這兩個條件都滿足的情況下:
判斷 B 的 prototype 屬性指向的原型物件(B.prototype)是否在物件 A 的原型鏈上。
如果在,則為 true;如果不在,則為 false。
下面我們舉一個例子一步步來說明:
function Person() {}
const p1 = new Person();
p1 instanceof Person; // true
複製程式碼
第一步:每一個建構函式都有一個 prototype
屬性。
![instanceof-01](https://i.iter01.com/images/2a303f6825a7665b2b32a06ac131d02393553b84007e87567b37c140df1c28bb.png)
第二步:這個 prototype
屬性指向這個建構函式的原型物件
![instanceof-02](https://i.iter01.com/images/04bb5332de8f10427f44bc94a8d2a4b69c436001c466d627400f6a986ab19a20.png)
第三步:通過 new
關鍵字,可以建立一個建構函式的例項(這裡是 p1),而例項上都有一個 __proto__
屬性
![instanceof-03](https://i.iter01.com/images/3c276b3446e97e4cdffc28e2fb3a2b6f935e1042a77b92790060374a087f3bf5.png)
第四步:例項上的 __proto__
屬性也指向建構函式的原型物件,這樣我們就可以得到一張完整的關係圖了
![instanceof-04](https://i.iter01.com/images/b74a8eec02970ecae6703cb89b34a884e7a03b0ebaad8eb79510977ba15e250f.png)
第五步:p1 instanceof Person
,檢查 B(Person) 的 prototype
屬性指向的原型物件,是否在物件 A(p1) 的原型鏈上。
![instanceof-05](https://i.iter01.com/images/82c19632c7c97023a380b3bf2d9f6ce8e8b51f5686a479ca889633c43f362a2d.png)
經過我們的一步步分解,發現 B(Person) 的 prototype
所指向的原型物件確實在 A(p1) 的原型鏈上,所以我們可以確定 p1 instanceof Person
一定是為 true
的。
我們再深入一點會發現,不僅僅 p1 instanceof Person
為 true
,p1 instanceof Object
也為 true
,這又是為什麼呢?
其實,Person
的原型物件上也有一個 __proto__
屬性,而這個屬性指向 Object
的 prototype
屬性所指向的原型物件,我們可以在控制檯列印一下:
![instanceof-06](https://i.iter01.com/images/8de7d686718a5872049d1b504666443a3bbae1762b5de69cfb07b08ad86abdd7.png)
既然有這個關係,那我們再完善一下上面的圖:
![instanceof-07](https://i.iter01.com/images/5c0a56675ef7b753e58e4fbda150e1ccc8c4252c706de2aafcc537c242f85edd.png)
通過 Person
的例子,我們知道建構函式 Object
上的 prototype
屬性會指向它的原型物件:
![instanceof-08](https://i.iter01.com/images/a30b91f2c1708fc4ed9fe216ae9dd17530eb69496b34e1d13097cadc05a0581c.png)
現在,我們要判斷 p1 instanceof Object
的真假,還記得上面的定義麼?我們再來一遍:
判斷 B 的 prototype 屬性指向的原型物件(B.prototype)是否在物件 A 的原型鏈上。
如果在,則為 true;如果不在,則為 false。
此時,我們發現 B(Object) 的 prototype
屬性所指向的原型物件依然在 A(p1) 的原型鏈上,所以結果為 true
。
![instanceof-09](https://i.iter01.com/images/34f18a65a9b1411f8e6ec2539e5b649651b46957536a9c931dcd70d6df1f7bc2.png)
通過上面的例子我們可以知道,其實 instanceof
的原理非常簡單,就是一個查詢原型鏈的過程,所以只要你理解了原型鏈的相關知識,理解 instanceof
的原理就不會再有問題了。這裡我們稍微總結兩點與instanceof
有關的原型鏈知識:
- 所有 JavaScript 物件都有
__proto__
屬性,只有Object.prototype.__proto__ === null
; - 建構函式的
prototype
屬性指向它的原型物件,而建構函式例項的__proto__
屬性也指向該原型物件;
二、如何實現一個 instanceof ?
看了上面的過程,其實也很容易給出 instanceof
的實現方式:
function instance_of(left, right) {
const RP = right.prototype; // 建構函式的原型
while(true) {
if (left === null) {
return false;
}
if (left === RP) { // 一定要嚴格比較
return true;
}
left = left.__proto__; // 沿著原型鏈重新賦值
}
}
複製程式碼
有了上面的實現方法,我們再解釋一下上面的例子:
function Person() {}
const p1 = new Person();
p1 instanceof Object; // 用上面的程式碼解釋它
複製程式碼
第一次賦值
left = p1
right = Object
RP = Object.prototype
複製程式碼
第一次判斷
left !== null
並且 left !== RP
,繼續向上尋找 left
的原型鏈,準備新的賦值。
第二次賦值
left = p1.__proto__ = Person.prototype
複製程式碼
第二次判斷
left !== null
並且 left !== RP
,繼續向上尋找 left
的原型鏈,準備新的賦值。
第三次賦值
left = p1.__proto__.__proto__ = Person.prototype.__proto__
複製程式碼
第三次賦值
left !== null
,此時 left === RP
,返回 true
,函式執行完畢。
三、總結
今天,我們用一個例子,通過畫圖以及程式碼實現兩個角度剖析了 instanceof
的實現原理,其實思路也很簡單,無非就是一個沿原型鏈向上查詢的過程。希望大家可以在以後的面試過程中,不再被" instanceof
的實現原理是什麼?"這樣的面試難住了。
當然,閱讀永遠都只是一種十分被動的學習方法,我還是建議你能自己實踐一下。在文章的開頭有幾個例子,感興趣的同學可以挑選一個例子,自己通過畫圖以及程式碼實現兩種方式再加深一遍理解,相信你會理解的更深刻。
如果文章中錯誤或表述不嚴謹的地方,歡迎指正。
最後,文章會首先發布在我的 Github ,以及公眾號上,歡迎關注。
!["instanceof 的原理是什麼"?大聲告訴面試官,我知道!](https://i.iter01.com/images/1f8847a18857f72b2eb01c88acbdaeec0044e2c1b703278754c71d084cc6182b.jpg)