ECMAScript7規範中的instanceof操作符
本文主要講解ECMAScript7
規範中的instanceof
操作符。
預備知識
有名的Symbols
“有名”的Symbols
指的是內建的符號,它們定義在Symbol
物件上。ECMAScript7
中使用了@@name
的形式引用這些內建的符號,比如下面會提到的@@hasInstance
,其實就是Symbol.hasInstance
。
InstanceofOperator(O, C)
O instanceof C
在內部會呼叫InstanceofOperator(O, C)
抽象操作,該抽象操作的步驟如下:
- 如果
C
的資料型別不是物件,丟擲一個型別錯誤的異常; - 讓
instOfHandler
等於GetMethod(C, @@hasInstance)
C
的@@hasInstance
屬性的值; -
如果
instOfHandler
的值不是undefined
,那麼:- 返回
ToBoolean(? Call(instOfHandler, C, « O »))
的結果,大概語義就是執行instOfHandler(O)
,然後把呼叫結果強制轉化為布林型別返回。
- 返回
- 如果
C
不能被呼叫,丟擲一個型別錯誤的異常; - 返回
OrdinaryHasInstance(C, O)
的結果。
OrdinaryHasInstance(C, O)
OrdinaryHasInstance(C, O)
抽象操作的步驟如下:
- 如果
C
不能被呼叫,返回false
; -
如果
C
[[BoundTargetFunction]]
,那麼:- 讓
BC
等於C
的內部插槽[[BoundTargetFunction]]
的值; - 返回
InstanceofOperator(O, BC)
的結果;
- 讓
- 如果
O
的型別不是物件,返回false
; - 讓
P
等於Get(C, "prototype")
,大概語義是獲取C.prototype
的值; - 如果
P
的資料型別不是物件,丟擲一個型別錯誤的異常; -
重複執行下述步驟:
- 讓
O
等於O.[[GetPrototypeOf]]()
的結果,大概語義就是獲取O
的原型物件; - 如果
O
等於null
,返回false
; - 如果
SameValue(P, O)
的結果是true
true
。
- 讓
SameValue
抽象操作參見JavaScript中的==,===和Object.js()中的Object.is()
,Object.is()
使用的就是這個抽象操作的結果。
由上述步驟2
可知,如果C
是一個bind
函式,那麼會重新在C
繫結的目標函式上執行InstanceofOperator(O, BC)
操作。
由上述步驟6
可知,會重複地獲取物件O
的原型物件,然後比較該原型物件和C
的prototype
屬性是否相等,直到相等返回true
,或者O
變為null
,也就是遍歷完整個原型鏈,返回false
。
Function.prototype[@@hasInstance](V)
由上面的InstanceofOperator(O, C)
抽象操作的步驟2
和3
可以知道,如果C
上面定義或繼承了@@ hasInstance
屬性的話,會呼叫該屬性的值,而不會走到步驟4
和5
。步驟4
和5
的目的是為了相容沒有實現@@hasInstance
方法的瀏覽器。如果一個函式沒有定義或繼承@@hasInstance
屬性,那麼就會使用預設的instanceof
的語義,也就是OrdinaryHasInstance(C, O)
抽象操作描述的步驟。
ECMAScript7
規範中,在Function
的prototype
屬性上定義了@@hasInstance
屬性。Function.prototype[@@hasInstance](V)
的步驟如下:
- 讓
F
等於this
值; - 返回
OrdinaryHasInstance(F, V)
的結果。
所以,你可以看到在預設情況下,instanceof
的語義是一樣的,都是返回OrdinaryHasInstance(F, V)
的結果。為什麼說預設情況下?因為你可以覆蓋Function.prototype[@@hasInstance]
方法,去自定義instanceof
的行為。
例子
function A () {}
function B () {}
var a = new A
a.__proto__ === A.prototype // true
a.__proto__.__proto__ === Object.prototype // true
a.__proto__.__proto__.__proto__ === null // true
a instanceof A // true
a instanceof B // false
由OrdinaryHasInstance(C, O)
的第6
步可知:
- 對於
a instanceof A
,P
是A.prototype
,在第一次迴圈的時候,a
的原型物件a._proto__
是A.prototype
,也就是步驟中的O
是A.prototype
,所以返回了true
; - 對於
a instanceof B
,P
是B.prototype
,在第一次迴圈的時候,a
的原型物件a._proto__
是A.prototype
,不等於P
;執行第二次迴圈,此時O
是a.__proto__.__proto__
,也就是Object.prototype
,不等於P
;執行第三次迴圈,此時O
是a.__proto__.__proto__.__proto__
,也就是null
,也就是原型鏈都遍歷完了,所以返回了false
。
接著上面的例子:
A.prototype.__proto__ = B.prototype
a.__proto__ === A.prototype // true
a.__proto__.__proto__ === B.prototype // true
a.__proto__.__proto__.__proto__ === Object.prototype // true
a.__proto__.__proto__.__proto__.__proto__ === null // true
a instanceof B // true
在上面的例子中,我們把B.prototype
設定成了a
的原型鏈中的一環,這樣a instanceof B
在OrdinaryHasInstance(C, O)
的第6
步的第2
次迴圈的時候,返回了true
。
由OrdinaryHasInstance(C, O)
的第2
步,我們知道bind
函式的行為和普通函式的行為是不一樣的:
function A () {}
var B = A.bind()
B.prototype === undefined // true
var b = new B
b instanceof B // true
b instanceof A // true
由上面的例子可知,B.prototype
是undefined
。所以,instanceof
作用於bind
函式的返回結果其實是作用於繫結的目標函式的返回值,和bind
函式基本上沒有什麼關係。
由InstanceofOperator(O, C)
步驟2
和步驟3
可知,我們可以通過覆蓋原型上的@@hasInstance
方法來自定義instanceof
的行為:
function A () {}
var a = new A
a instanceof A // true
A[Symbol.hasInstance] = function () { return false }
a instanceof A // ?
在chrome
瀏覽器測試了一下,發現還是輸出true
。然後看了一下ECMAScript6
的文件,ECMAScript6
文件裡面還沒有規定可以通過@@hasInstance
改變instanceof
的行為,所以應該是目前chrome
瀏覽器還沒有實現ECMAScript7
中的instanceof
操作符的行為。
總結
本文主要講解ECMAScript7
規範中的instanceof
操作符,希望大家能有所收穫。如果本文有什麼錯誤或者不嚴謹的地方,歡迎在評論區留言。