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:
-
Pokud existuje statická metoda
Symbol.hasInstance
, pak to zavolejte:Class[Symbol.hasInstance](obj)
. Mělo by vrátit buďtrue
nebofalse
, 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
-
Většina tříd nemá
Symbol.hasInstance
. V takovém případě se použije standardní logika:obj instanceOf Class
zkontroluje, zdaClass.prototype
se rovná jednomu z prototypů vobj
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.