1. 程式人生 > >ECMAScript7規範中的instanceof操作符

ECMAScript7規範中的instanceof操作符

本文主要講解ECMAScript7規範中的instanceof操作符。

預備知識

有名的Symbols

“有名”的Symbols指的是內建的符號,它們定義在Symbol物件上。ECMAScript7中使用了@@name的形式引用這些內建的符號,比如下面會提到的@@hasInstance,其實就是Symbol.hasInstance

InstanceofOperator(O, C)

O instanceof C在內部會呼叫InstanceofOperator(O, C)抽象操作,該抽象操作的步驟如下:

  1. 如果C的資料型別不是物件,丟擲一個型別錯誤的異常;
  2. instOfHandler等於GetMethod(C, @@hasInstance)
    ,大概語義就是獲取物件C@@hasInstance屬性的值;
  3. 如果instOfHandler的值不是undefined,那麼:

    1. 返回ToBoolean(? Call(instOfHandler, C, « O »))的結果,大概語義就是執行instOfHandler(O),然後把呼叫結果強制轉化為布林型別返回。
  4. 如果C不能被呼叫,丟擲一個型別錯誤的異常;
  5. 返回OrdinaryHasInstance(C, O)的結果。

OrdinaryHasInstance(C, O)

OrdinaryHasInstance(C, O)抽象操作的步驟如下:

  1. 如果C不能被呼叫,返回false
  2. 如果C

    有內部插槽[[BoundTargetFunction]],那麼:

    1. BC等於C的內部插槽[[BoundTargetFunction]]的值;
    2. 返回InstanceofOperator(O, BC)的結果;
  3. 如果O的型別不是物件,返回false
  4. P等於Get(C, "prototype"),大概語義是獲取C.prototype的值;
  5. 如果P的資料型別不是物件,丟擲一個型別錯誤的異常;
  6. 重複執行下述步驟:

    1. O等於O.[[GetPrototypeOf]]()的結果,大概語義就是獲取O的原型物件;
    2. 如果O等於null,返回false
    3. 如果SameValue(P, O)的結果是true
      ,返回true

SameValue抽象操作參見JavaScript中的==,===和Object.js()中的Object.is()Object.is()使用的就是這個抽象操作的結果。

由上述步驟2可知,如果C是一個bind函式,那麼會重新在C繫結的目標函式上執行InstanceofOperator(O, BC)操作。

由上述步驟6可知,會重複地獲取物件O的原型物件,然後比較該原型物件和Cprototype屬性是否相等,直到相等返回true,或者O變為null,也就是遍歷完整個原型鏈,返回false

Function.prototype[@@hasInstance](V)

由上面的InstanceofOperator(O, C)抽象操作的步驟23可以知道,如果C上面定義或繼承了@@ hasInstance屬性的話,會呼叫該屬性的值,而不會走到步驟45。步驟45的目的是為了相容沒有實現@@hasInstance方法的瀏覽器。如果一個函式沒有定義或繼承@@hasInstance屬性,那麼就會使用預設的instanceof的語義,也就是OrdinaryHasInstance(C, O)抽象操作描述的步驟。

ECMAScript7規範中,在Functionprototype屬性上定義了@@hasInstance屬性。Function.prototype[@@hasInstance](V)的步驟如下:

  1. F等於this值;
  2. 返回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 APA.prototype,在第一次迴圈的時候,a的原型物件a._proto__A.prototype,也就是步驟中的OA.prototype,所以返回了true
  • 對於a instanceof BPB.prototype,在第一次迴圈的時候,a的原型物件a._proto__A.prototype,不等於P;執行第二次迴圈,此時Oa.__proto__.__proto__,也就是Object.prototype,不等於P;執行第三次迴圈,此時Oa.__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 BOrdinaryHasInstance(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.prototypeundefined。所以,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操作符,希望大家能有所收穫。如果本文有什麼錯誤或者不嚴謹的地方,歡迎在評論區留言。