Kontrola třídy:instanceof

instanceof operátor umožňuje zkontrolovat, zda objekt patří do určité třídy. Zohledňuje také dědičnost.

Taková kontrola může být v mnoha případech nezbytná. Může být například použit pro vytvoření polymorfního funkce, která zachází s argumenty odlišně v závislosti na jejich typu.

Operátor instanceof

Syntaxe je:

obj instanceof Class

Vrátí true pokud obj patří do Class nebo třída, která z něj dědí.

Například:

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

// is it an object of Rabbit class?
alert( rabbit instanceof Rabbit ); // true

Funguje také s funkcemi konstruktoru:

// instead of class
function Rabbit() {}

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

…A s vestavěnými třídami jako Array :

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

Vezměte prosím na vědomí, že arr také patří do Object třída. Je to proto, že Array prototypicky dědí z Object .

Normálně instanceof prozkoumá prototypový řetězec pro kontrolu. Můžeme také nastavit vlastní logiku ve statické metodě Symbol.hasInstance .

Algoritmus obj instanceof Class funguje zhruba takto:

  1. Pokud existuje statická metoda Symbol.hasInstance , pak to zavolejte:Class[Symbol.hasInstance](obj) . Mělo by vrátit buď true nebo false , a máme hotovo. Takto můžeme přizpůsobit chování instanceof .

    Například:

    // setup instanceOf check that assumes that
    // anything with canEat property is an 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) is called
  2. Většina tříd nemá Symbol.hasInstance . V takovém případě se použije standardní logika:obj instanceOf Class zkontroluje, zda Class.prototype se rovná jednomu z prototypů v obj prototypový řetězec.

    Jinými slovy, porovnávejte jeden po druhém:

    obj.__proto__ === Class.prototype?
    obj.__proto__.__proto__ === Class.prototype?
    obj.__proto__.__proto__.__proto__ === Class.prototype?
    ...
    // if any answer is true, return true
    // otherwise, if we reached the end of the chain, return false

    Ve výše uvedeném příkladu rabbit.__proto__ === Rabbit.prototype , takže odpověď poskytuje okamžitě.

    V případě dědictví bude shoda na druhém kroku:

    class Animal {}
    class Rabbit extends Animal {}
    
    let rabbit = new Rabbit();
    alert(rabbit instanceof Animal); // true
    
    // rabbit.__proto__ === Animal.prototype (no match)
    // rabbit.__proto__.__proto__ === Animal.prototype (match!)

Zde je ilustrace toho, co rabbit instanceof Animal ve srovnání s Animal.prototype :

Mimochodem, existuje také metoda objA.isPrototypeOf(objB), která vrací true pokud objA je někde v řetězci prototypů pro objB . Takže test obj instanceof Class lze přeformulovat jako Class.prototype.isPrototypeOf(obj) .

Je to legrační, ale Class samotný konstruktor se kontroly neúčastní! Pouze řetězec prototypů a Class.prototype záleží.

To může vést k zajímavým důsledkům, když prototype vlastnost se změní po vytvoření objektu.

Jako zde:

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

// changed the prototype
Rabbit.prototype = {};

// ...not a rabbit any more!
alert( rabbit instanceof Rabbit ); // false

Bonus:Object.prototype.toString pro typ

Již víme, že prosté objekty jsou převedeny na řetězec jako [object Object] :

let obj = {};

alert(obj); // [object Object]
alert(obj.toString()); // the same

To je jejich implementace toString . Existuje však skrytá funkce, díky které je toString ve skutečnosti mnohem silnější než to. Můžeme jej použít jako rozšířený typeof a alternativa pro instanceof .

Zní to divně? Vskutku. Pojďme demystifikovat.

Podle specifikace vestavěný toString lze extrahovat z objektu a spustit v kontextu jakékoli jiné hodnoty. A jeho výsledek závisí na této hodnotě.

  • U čísla to bude [object Number]
  • Pro logickou hodnotu to bude [object Boolean]
  • Pro null :[object Null]
  • Pro undefined :[object Undefined]
  • Pro pole:[object Array]
  • …atd (přizpůsobitelné).

Pojďme si ukázat:

// copy toString method into a variable for convenience
let objectToString = Object.prototype.toString;

// what type is this?
let arr = [];

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

Zde jsme použili volání, jak je popsáno v kapitole Dekorátoři a přesměrování, volání/aplikace pro provedení funkce objectToString v kontextu this=arr .

Interně toString algoritmus zkoumá this a vrátí odpovídající výsledek. Další příklady:

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

Chování objektu toString lze upravit pomocí speciální vlastnosti objektu Symbol.toStringTag .

Například:

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

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

Pro většinu objektů specifických pro prostředí taková vlastnost existuje. Zde jsou některé příklady specifické pro prohlížeč:

// toStringTag for the environment-specific object and class:
alert( window[Symbol.toStringTag]); // Window
alert( XMLHttpRequest.prototype[Symbol.toStringTag] ); // XMLHttpRequest

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

Jak vidíte, výsledek je přesně Symbol.toStringTag (pokud existuje), zabalený do [object ...] .

Na konci máme „typeof on steroids“, který funguje nejen pro primitivní datové typy, ale také pro vestavěné objekty a dokonce jej lze přizpůsobit.

Můžeme použít {}.toString.call místo instanceof pro vestavěné objekty, když chceme získat typ jako řetězec, nikoli jen pro kontrolu.

Shrnutí

Pojďme si shrnout metody kontroly typu, které známe:

funguje pro vrací
typeof primitiva řetězec
{}.toString primitiva, vestavěné objekty, objekty s Symbol.toStringTag řetězec
instanceof objekty pravda/nepravda

Jak vidíme, {}.toString je technicky „pokročilejší“ typeof .

A instanceof Operátor skutečně září, když pracujeme s hierarchií tříd a chceme třídu zkontrolovat s ohledem na dědičnost.