Javascript 類檢查:"instanceof"

2023-02-17 10:53 更新

?instanceof? 操作符用于檢查一個(gè)對(duì)象是否屬于某個(gè)特定的 class。同時(shí),它還考慮了繼承。

在許多情況下,可能都需要進(jìn)行此類檢查。例如,它可以被用來(lái)構(gòu)建一個(gè) 多態(tài)性(polymorphic) 的函數(shù),該函數(shù)根據(jù)參數(shù)的類型對(duì)參數(shù)進(jìn)行不同的處理。

instanceof 操作符

語(yǔ)法:

obj instanceof Class

如果 obj 隸屬于 Class 類(或 Class 類的衍生類),則返回 true。

例如:

class Rabbit {}
let rabbit = new Rabbit();

// rabbit 是 Rabbit class 的對(duì)象嗎?
alert( rabbit instanceof Rabbit ); // true

它還可以與構(gòu)造函數(shù)一起使用:

// 這里是構(gòu)造函數(shù),而不是 class
function Rabbit() {}

alert( new Rabbit() instanceof Rabbit ); // true

……與諸如 Array 之類的內(nèi)建 class 一起使用:

let arr = [1, 2, 3];
alert( arr instanceof Array ); // true
alert( arr instanceof Object ); // true

有一點(diǎn)需要留意,arr 同時(shí)還隸屬于 Object 類。因?yàn)閺脑蜕蟻?lái)講,Array 是繼承自 Object 的。

通常,instanceof 在檢查中會(huì)將原型鏈考慮在內(nèi)。此外,我們還可以在靜態(tài)方法 Symbol.hasInstance 中設(shè)置自定義邏輯。

obj instanceof Class 算法的執(zhí)行過(guò)程大致如下:

  1. 如果這兒有靜態(tài)方法 ?Symbol.hasInstance?,那就直接調(diào)用這個(gè)方法:
  2. 例如:

    // 設(shè)置 instanceOf 檢查
    // 并假設(shè)具有 canEat 屬性的都是 animal
    class Animal {
      static [Symbol.hasInstance](obj) {
        if (obj.canEat) return true;
      }
    }
    
    let obj = { canEat: true };
    
    alert(obj instanceof Animal); // true:Animal[Symbol.hasInstance](obj) 被調(diào)用
  3. 大多數(shù) class 沒有 ?Symbol.hasInstance?。在這種情況下,標(biāo)準(zhǔn)的邏輯是:使用 ?obj instanceOf Class? 檢查 ?Class.prototype? 是否等于 ?obj? 的原型鏈中的原型之一。
  4. 換句話說(shuō)就是,一個(gè)接一個(gè)地比較:

    obj.__proto__ === Class.prototype?
    obj.__proto__.__proto__ === Class.prototype?
    obj.__proto__.__proto__.__proto__ === Class.prototype?
    ...
    // 如果任意一個(gè)的答案為 true,則返回 true
    // 否則,如果我們已經(jīng)檢查到了原型鏈的尾端,則返回 false

    在上面那個(gè)例子中,rabbit.__proto__ === Rabbit.prototype,所以立即就給出了結(jié)果。

    而在繼承的例子中,匹配將在第二步進(jìn)行:

    class Animal {}
    class Rabbit extends Animal {}
    
    let rabbit = new Rabbit();
    alert(rabbit instanceof Animal); // true
    
    // rabbit.__proto__ === Animal.prototype(無(wú)匹配)
    // rabbit.__proto__.__proto__ === Animal.prototype(匹配?。?/code>

下圖展示了 rabbit instanceof Animal 的執(zhí)行過(guò)程中,Animal.prototype 是如何參與比較的:


這里還要提到一個(gè)方法 objA.isPrototypeOf(objB),如果 objA 處在 objB 的原型鏈中,則返回 true。所以,可以將 obj instanceof Class 檢查改為 Class.prototype.isPrototypeOf(obj)。

這很有趣,但是 Class 的 constructor 自身是不參與檢查的!檢查過(guò)程只和原型鏈以及 Class.prototype 有關(guān)。

創(chuàng)建對(duì)象后,如果更改 prototype 屬性,可能會(huì)導(dǎo)致有趣的結(jié)果。

就像這樣:

function Rabbit() {}
let rabbit = new Rabbit();

// 修改了 prototype
Rabbit.prototype = {};

// ...再也不是 rabbit 了!
alert( rabbit instanceof Rabbit ); // false

福利:使用 Object.prototype.toString 方法來(lái)揭示類型

大家都知道,一個(gè)普通對(duì)象被轉(zhuǎn)化為字符串時(shí)為 [object Object]

let obj = {};

alert(obj); // [object Object]
alert(obj.toString()); // 同上

這是通過(guò) toString 方法實(shí)現(xiàn)的。但是這兒有一個(gè)隱藏的功能,該功能可以使 toString 實(shí)際上比這更強(qiáng)大。我們可以將其作為 typeof 的增強(qiáng)版或者 instanceof 的替代方法來(lái)使用。

聽起來(lái)挺不可思議?那是自然,精彩馬上揭曉。

按照 規(guī)范 所講,內(nèi)建的 toString 方法可以被從對(duì)象中提取出來(lái),并在任何其他值的上下文中執(zhí)行。其結(jié)果取決于該值。

  • 對(duì)于 number 類型,結(jié)果是 ?[object Number]?
  • 對(duì)于 boolean 類型,結(jié)果是 ?[object Boolean]?
  • 對(duì)于 ?null?:?[object Null]?
  • 對(duì)于 ?undefined?:?[object Undefined]?
  • 對(duì)于數(shù)組:?[object Array]?
  • ……等(可自定義)

讓我們演示一下:

// 方便起見,將 toString 方法復(fù)制到一個(gè)變量中
let objectToString = Object.prototype.toString;

// 它是什么類型的?
let arr = [];

alert( objectToString.call(arr) ); // [object Array]

這里我們用到了在 裝飾器模式和轉(zhuǎn)發(fā),call/apply 一章中講過(guò)的 call 方法來(lái)在上下文 this=arr 中執(zhí)行函數(shù) objectToString。

在內(nèi)部,toString 的算法會(huì)檢查 this,并返回相應(yīng)的結(jié)果。再舉幾個(gè)例子:

let s = Object.prototype.toString;

alert( s.call(123) ); // [object Number]
alert( s.call(null) ); // [object Null]
alert( s.call(alert) ); // [object Function]

Symbol.toStringTag

可以使用特殊的對(duì)象屬性 Symbol.toStringTag 自定義對(duì)象的 toString 方法的行為。

例如:

let user = {
  [Symbol.toStringTag]: "User"
};

alert( {}.toString.call(user) ); // [object User]

對(duì)于大多數(shù)特定于環(huán)境的對(duì)象,都有一個(gè)這樣的屬性。下面是一些特定于瀏覽器的示例:

// 特定于環(huán)境的對(duì)象和類的 toStringTag:
alert( window[Symbol.toStringTag]); // Window
alert( XMLHttpRequest.prototype[Symbol.toStringTag] ); // XMLHttpRequest

alert( {}.toString.call(window) ); // [object Window]
alert( {}.toString.call(new XMLHttpRequest()) ); // [object XMLHttpRequest]

正如我們所看到的,輸出結(jié)果恰好是 Symbol.toStringTag(如果存在),只不過(guò)被包裹進(jìn)了 [object ...] 里。

這樣一來(lái),我們手頭上就有了個(gè)“磕了藥似的 typeof”,不僅能檢查原始數(shù)據(jù)類型,而且適用于內(nèi)建對(duì)象,更可貴的是還支持自定義。

所以,如果我們想要獲取內(nèi)建對(duì)象的類型,并希望把該信息以字符串的形式返回,而不只是檢查類型的話,我們可以用 {}.toString.call 替代 instanceof

總結(jié)

讓我們總結(jié)一下我們知道的類型檢查方法:

用于 返回值
typeof 原始數(shù)據(jù)類型 string
{}.toString 原始數(shù)據(jù)類型,內(nèi)建對(duì)象,包含 Symbol.toStringTag 屬性的對(duì)象 string
instanceof 對(duì)象 true/false

正如我們所看到的,從技術(shù)上講,{}.toString 是一種“更高級(jí)的” typeof。

當(dāng)我們使用類的層次結(jié)構(gòu)(hierarchy),并想要對(duì)該類進(jìn)行檢查,同時(shí)還要考慮繼承時(shí),這種場(chǎng)景下 instanceof 操作符確實(shí)很出色。

任務(wù)


不按套路出牌的 instanceof

重要程度: 5

在下面的代碼中,為什么 instanceof 會(huì)返回 true?我們可以明顯看到,a 并不是通過(guò) B() 創(chuàng)建的。

function A() {}
function B() {}

A.prototype = B.prototype = {};

let a = new A();

alert( a instanceof B ); // true

解決方案

是的,看起來(lái)確實(shí)很奇怪。

instanceof 并不關(guān)心函數(shù),而是關(guān)心函數(shù)的與原型鏈匹配的 prototype。

并且,這里 a.__proto__ == B.prototype,所以 instanceof 返回 true。

總之,根據(jù) instanceof 的邏輯,真正決定類型的是 prototype,而不是構(gòu)造函數(shù)。


以上內(nèi)容是否對(duì)您有幫助:
在線筆記
App下載
App下載

掃描二維碼

下載編程獅App

公眾號(hào)
微信公眾號(hào)

編程獅公眾號(hào)