Mit dem instanceof
kann überprüft werden, ob ein Objekt zu einer bestimmten Klasse gehört. Es berücksichtigt auch die Vererbung.
Eine solche Prüfung kann in vielen Fällen notwendig sein. Es kann beispielsweise zum Erstellen einer polymorphen Funktion verwendet werden, die Argumente je nach Typ unterschiedlich behandelt.
Die Syntax lautet:
obj-Instanz der Klasse
Es gibt true
zurück, wenn obj
zur Class
oder einer von ihr erbenden Klasse gehört.
Zum Beispiel:
Klasse Kaninchen {} let Rabbit = new Rabbit(); // Ist es ein Objekt der Rabbit-Klasse? Alert( Rabbit-Instanz von Rabbit ); // WAHR
Es funktioniert auch mit Konstruktorfunktionen:
// statt Klasse Funktion Rabbit() {} Alert( new Rabbit() Instanz von Rabbit ); // WAHR
…Und mit integrierten Klassen wie Array
:
sei arr = [1, 2, 3]; Alert( arr-Instanz des Arrays ); // WAHR Alert( arr Instanz des Objekts ); // WAHR
Bitte beachten Sie, dass arr
auch zur Object
-Klasse gehört. Das liegt daran, dass Array
prototypisch von Object
erbt.
Normalerweise untersucht instanceof
für die Prüfung die Prototypenkette. Wir können auch eine benutzerdefinierte Logik in der statischen Methode Symbol.hasInstance
festlegen.
Der Algorithmus der obj instanceof Class
funktioniert ungefähr wie folgt:
Wenn es eine statische Methode Symbol.hasInstance
gibt, nennen Sie sie einfach: Class[Symbol.hasInstance](obj)
. Es sollte entweder true
oder false
zurückgeben, und wir sind fertig. Auf diese Weise können wir das Verhalten von instanceof
anpassen.
Zum Beispiel:
// Setup-InstanceOf-Prüfung, die das voraussetzt // Alles mit der Eigenschaft canEat ist ein Tier Klasse Tier { static [Symbol.hasInstance](obj) { if (obj.canEat) return true; } } let obj = { canEat: true }; Alert(obj-Instanz von Animal); // true: Animal[Symbol.hasInstance](obj) wird aufgerufen
Die meisten Klassen verfügen nicht über Symbol.hasInstance
. In diesem Fall wird die Standardlogik verwendet: obj instanceOf Class
prüft, ob Class.prototype
einem der Prototypen in der obj
Prototypkette entspricht.
Mit anderen Worten, vergleichen Sie nacheinander:
obj.__proto__ === Class.prototype? obj.__proto__.__proto__ === Class.prototype? obj.__proto__.__proto__.__proto__ === Class.prototype? ... // wenn eine Antwort wahr ist, gebe wahr zurück // andernfalls, wenn wir das Ende der Kette erreicht haben, false zurückgeben
Im obigen Beispiel rabbit.__proto__ === Rabbit.prototype
, sodass die Antwort sofort angezeigt wird.
Im Falle einer Erbschaft erfolgt die Übereinstimmung im zweiten Schritt:
Klasse Tier {} Klasse Rabbit erweitert Animal {} let Rabbit = new Rabbit(); Alert(Rabbit-Instanz von Animal); // WAHR // Rabbit.__proto__ === Animal.prototype (keine Übereinstimmung) // Rabbit.__proto__.__proto__ === Animal.prototype (Übereinstimmung!)
Hier ist die Veranschaulichung dessen, was rabbit instanceof Animal
mit Animal.prototype
vergleicht:
Übrigens gibt es auch eine Methode objA.isPrototypeOf(objB), die true
zurückgibt, wenn sich objA
irgendwo in der Prototypenkette für objB
befindet. Daher kann der Test der obj instanceof Class
als Class.prototype.isPrototypeOf(obj)
umformuliert werden.
Es ist lustig, aber der Class
selbst nimmt nicht an der Prüfung teil! Nur die Kette von Prototypen und Class.prototype
ist wichtig.
Dies kann zu interessanten Konsequenzen führen, wenn eine prototype
nach der Erstellung des Objekts geändert wird.
Wie hier:
Funktion Rabbit() {} let Rabbit = new Rabbit(); // den Prototyp geändert Rabbit.prototype = {}; // ...kein Kaninchen mehr! Alert( Rabbit-Instanz von Rabbit ); // FALSCH
Wir wissen bereits, dass einfache Objekte als [object Object]
in Strings umgewandelt werden:
sei obj = {}; alarm(obj); // [Objekt Objekt] alarm(obj.toString()); // das gleiche
Das ist ihre Implementierung von toString
. Aber es gibt eine versteckte Funktion, die toString
tatsächlich viel leistungsfähiger macht. Wir können es als erweiterten typeof
und als Alternative für instanceof
verwenden.
Klingt seltsam? In der Tat. Lassen Sie uns entmystifizieren.
Durch Angabe kann der integrierte toString
aus dem Objekt extrahiert und im Kontext eines beliebigen anderen Werts ausgeführt werden. Und sein Ergebnis hängt von diesem Wert ab.
Für eine Zahl ist es [object Number]
Für einen booleschen Wert ist es [object Boolean]
Für null
: [object Null]
Für undefined
: [object Undefined]
Für Arrays: [object Array]
…usw. (anpassbar).
Lassen Sie uns Folgendes demonstrieren:
// der Einfachheit halber die toString-Methode in eine Variable kopieren let objectToString = Object.prototype.toString; // welcher Typ ist das? sei arr = []; alarm( objectToString.call(arr) ); // [Objekt-Array]
Hier haben wir call wie im Kapitel Dekoratoren und Weiterleitung beschrieben, call/apply verwendet, um die Funktion objectToString
im Kontext this=arr
auszuführen.
Intern untersucht der toString
-Algorithmus this
und gibt das entsprechende Ergebnis zurück. Weitere Beispiele:
let s = Object.prototype.toString; alarm( s.call(123) ); // [Objektnummer] alarm( s.call(null) ); // [Objekt Null] alarm( s.call(alert) ); // [Objektfunktion]
Das Verhalten von Object toString
kann mithilfe einer speziellen Objekteigenschaft Symbol.toStringTag
angepasst werden.
Zum Beispiel:
let user = { [Symbol.toStringTag]: „Benutzer“ }; alarm( {}.toString.call(user) ); // [Objekt Benutzer]
Für die meisten umgebungsspezifischen Objekte gibt es eine solche Eigenschaft. Hier sind einige browserspezifische Beispiele:
// toStringTag für das umgebungsspezifische Objekt und die Klasse: alarm( window[Symbol.toStringTag]); // Fenster Alert( XMLHttpRequest.prototype[Symbol.toStringTag] ); // XMLHttpRequest alarm( {}.toString.call(window) ); // [Objektfenster] Alert( {}.toString.call(new XMLHttpRequest()) ); // [Objekt XMLHttpRequest]
Wie Sie sehen können, ist das Ergebnis genau Symbol.toStringTag
(falls vorhanden), verpackt in [object ...]
.
Am Ende haben wir „typeof on steroids“, das nicht nur für primitive Datentypen, sondern auch für integrierte Objekte funktioniert und sogar angepasst werden kann.
Wir können {}.toString.call
anstelle von instanceof
für integrierte Objekte verwenden, wenn wir den Typ als String erhalten möchten, anstatt ihn nur zu überprüfen.
Fassen wir die uns bekannten Methoden zur Typprüfung zusammen:
funktioniert für | kehrt zurück | |
---|---|---|
typeof | Primitiven | Zeichenfolge |
{}.toString | Grundelemente, integrierte Objekte, Objekte mit Symbol.toStringTag | Zeichenfolge |
instanceof | Objekte | wahr/falsch |
Wie wir sehen können, ist {}.toString
technisch gesehen ein „fortgeschrittenerer“ typeof
.
Und der Operator instanceof
glänzt wirklich, wenn wir mit einer Klassenhierarchie arbeiten und die Klasse unter Berücksichtigung der Vererbung prüfen möchten.
Wichtigkeit: 5
Warum gibt im Code unten instanceof
true
zurück? Wir können leicht erkennen, dass a
nicht von B()
erstellt wird.
Funktion A() {} Funktion B() {} A.prototype = B.prototype = {}; sei a = neues A(); Alert( eine Instanz von B ); // WAHR
Ja, sieht tatsächlich seltsam aus.
instanceof
kümmert sich jedoch nicht um die Funktion, sondern vielmehr um ihren prototype
, den sie mit der Prototypenkette abgleicht.
Und hier a.__proto__ == B.prototype
, also instanceof
true
zurück.
Nach der Logik von instanceof
definiert der prototype
also tatsächlich den Typ und nicht die Konstruktorfunktion.