Was passiert, wenn Objekte obj1 + obj2
hinzugefügt, obj1 - obj2
subtrahiert oder mit alert(obj)
gedruckt werden?
Mit JavaScript können Sie die Funktionsweise von Operatoren an Objekten nicht anpassen. Im Gegensatz zu einigen anderen Programmiersprachen wie Ruby oder C++ können wir keine spezielle Objektmethode implementieren, um Additionen (oder andere Operatoren) zu verarbeiten.
Bei solchen Operationen werden Objekte automatisch in Grundelemente umgewandelt, und dann wird die Operation über diese Grundelemente ausgeführt und führt zu einem Grundelementwert.
Das ist eine wichtige Einschränkung: Das Ergebnis von obj1 + obj2
(oder einer anderen mathematischen Operation) kann kein anderes Objekt sein!
Wir können beispielsweise keine Objekte erstellen, die Vektoren oder Matrizen (oder Erfolge oder was auch immer) darstellen, sie hinzufügen und als Ergebnis ein „summiertes“ Objekt erwarten. Solche architektonischen Meisterleistungen sind automatisch „von der Stange“.
Da wir hier also technisch nicht viel machen können, gibt es in realen Projekten keine Mathematik mit Objekten. Wenn es passiert, liegt es, von seltenen Ausnahmen abgesehen, an einem Codierungsfehler.
In diesem Kapitel behandeln wir, wie ein Objekt in ein Primitiv konvertiert wird und wie es angepasst wird.
Wir haben zwei Ziele:
Date
). Wir werden später auf sie stoßen.Im Kapitel Typkonvertierungen haben wir die Regeln für numerische, String- und boolesche Konvertierungen von Grundelementen gesehen. Aber wir haben eine Lücke für Objekte gelassen. Da wir nun über Methoden und Symbole Bescheid wissen, ist es möglich, es zu füllen.
true
. Es gibt nur numerische und String-Konvertierungen.Date
(die im Kapitel „Datum und Uhrzeit“ behandelt werden) subtrahiert werden, und das Ergebnis von date1 - date2
ist die Zeitdifferenz zwischen zwei Datumsangaben.alert(obj)
ausgeben und in ähnlichen Kontexten.Mithilfe spezieller Objektmethoden können wir die Konvertierung von Zeichenfolgen und Zahlen selbst implementieren.
Kommen wir nun zu den technischen Details, denn nur so kann das Thema vertieft behandelt werden.
Wie entscheidet JavaScript, welche Konvertierung angewendet werden soll?
Es gibt drei Varianten der Typkonvertierung, die in verschiedenen Situationen auftreten. Sie werden „Hinweise“ genannt, wie in der Spezifikation beschrieben:
"string"
Bei einer Objekt-zu-String-Konvertierung, wenn wir eine Operation für ein Objekt ausführen, das eine Zeichenfolge erwartet, wie etwa alert
:
// output alert(obj); // using object as a property key anotherObj[obj] = 123;
"number"
Für eine Objekt-in-Zahlen-Konvertierung, etwa wenn wir Mathematik machen:
// explicit conversion let num = Number(obj); // maths (except binary plus) let n = +obj; // unary plus let delta = date1 - date2; // less/greater comparison let greater = user1 > user2;
Die meisten integrierten mathematischen Funktionen umfassen auch eine solche Konvertierung.
"default"
Tritt in seltenen Fällen auf, wenn der Bediener „nicht sicher“ ist, welchen Typ er erwarten soll.
Beispielsweise kann das binäre Plus +
sowohl mit Zeichenfolgen (verkettet sie) als auch mit Zahlen (fügt sie hinzu) funktionieren. Wenn also ein binäres Plus ein Objekt als Argument erhält, verwendet es den "default"
-Hinweis, um es umzuwandeln.
Wenn außerdem ein Objekt mithilfe von ==
mit einer Zeichenfolge, Zahl oder einem Symbol verglichen wird, ist ebenfalls unklar, welche Konvertierung durchgeführt werden soll, sodass der "default"
-Hinweis verwendet wird.
// binary plus uses the "default" hint let total = obj1 + obj2; // obj == number uses the "default" hint if (user == 1) { ... };
Die größeren und kleineren Vergleichsoperatoren, wie z. B. <
>
, können auch sowohl mit Zeichenfolgen als auch mit Zahlen funktionieren. Dennoch verwenden sie den Hinweis "number"
und nicht "default"
. Das hat historische Gründe.
In der Praxis sind die Dinge jedoch etwas einfacher.
Alle integrierten Objekte außer einem Fall ( Date
, das erfahren wir später) implementieren "default"
-Konvertierung auf die gleiche Weise wie "number"
. Und wir sollten wahrscheinlich das Gleiche tun.
Dennoch ist es wichtig, alle drei Hinweise zu kennen, bald werden wir sehen, warum.
Um die Konvertierung durchzuführen, versucht JavaScript, drei Objektmethoden zu finden und aufzurufen:
obj[Symbol.toPrimitive](hint)
auf – die Methode mit dem symbolischen Schlüssel Symbol.toPrimitive
(Systemsymbol), falls eine solche Methode existiert,"string"
istobj.toString()
oder obj.valueOf()
aufzurufen, was auch immer vorhanden ist."number"
oder "default"
ist.obj.valueOf()
oder obj.toString()
aufzurufen, was auch immer vorhanden ist. Beginnen wir mit der ersten Methode. Es gibt ein integriertes Symbol namens Symbol.toPrimitive
, das zum Benennen der Konvertierungsmethode verwendet werden sollte, etwa so:
obj[Symbol.toPrimitive] = function(hint) { // here goes the code to convert this object to a primitive // it must return a primitive value // hint = one of "string", "number", "default" };
Wenn die Methode Symbol.toPrimitive
vorhanden ist, wird sie für alle Hinweise verwendet und es sind keine weiteren Methoden erforderlich.
Hier implementiert es beispielsweise ein user
:
let user = { name: "John", money: 1000, [Symbol.toPrimitive](hint) { alert(`hint: ${hint}`); return hint == "string" ? `{name: "${this.name}"}` : this.money; } }; // conversions demo: alert(user); // hint: string -> {name: "John"} alert(+user); // hint: number -> 1000 alert(user + 500); // hint: default -> 1500
Wie wir dem Code entnehmen können, wird user
je nach Konvertierung zu einer selbstbeschreibenden Zeichenfolge oder einem Geldbetrag. Die einzelne Methode user[Symbol.toPrimitive]
behandelt alle Konvertierungsfälle.
Wenn kein Symbol.toPrimitive
vorhanden ist, versucht JavaScript, die Methoden toString
und valueOf
zu finden:
"string"
-Hinweis: Rufen Sie die toString
-Methode auf. Wenn sie nicht existiert oder ein Objekt anstelle eines primitiven Werts zurückgibt, rufen Sie valueOf
auf (damit toString
die Priorität für String-Konvertierungen hat).valueOf
auf. Wenn es nicht existiert oder ein Objekt anstelle eines primitiven Werts zurückgibt, rufen Sie toString
auf (damit valueOf
die Priorität für Mathematik hat). Die Methoden toString
und valueOf
stammen aus der Antike. Es handelt sich dabei nicht um Symbole (Symbole gab es noch vor nicht allzu langer Zeit), sondern um „normale“ Methoden mit Stringnamen. Sie bieten eine alternative „alte“ Möglichkeit, die Konvertierung umzusetzen.
Diese Methoden müssen einen primitiven Wert zurückgeben. Wenn toString
oder valueOf
ein Objekt zurückgibt, wird es ignoriert (genauso, als ob es keine Methode gäbe).
Standardmäßig verfügt ein einfaches Objekt über die folgenden toString
und valueOf
-Methoden:
toString
Methode gibt einen String "[object Object]"
zurück.valueOf
Methode gibt das Objekt selbst zurück.Hier ist die Demo:
let user = {name: "John"}; alert(user); // [object Object] alert(user.valueOf() === user); // true
Wenn wir also versuchen, ein Objekt als Zeichenfolge zu verwenden, etwa in einer alert
oder so, dann sehen wir standardmäßig [object Object]
.
Der valueOf
wird hier nur der Vollständigkeit halber erwähnt, um Verwirrung zu vermeiden. Wie Sie sehen können, gibt es das Objekt selbst zurück und wird daher ignoriert. Fragen Sie mich nicht warum, das hat historische Gründe. Wir können also davon ausgehen, dass es nicht existiert.
Lassen Sie uns diese Methoden implementieren, um die Konvertierung anzupassen.
Hier macht user
beispielsweise dasselbe wie oben und verwendet eine Kombination aus toString
und valueOf
anstelle von Symbol.toPrimitive
:
let user = { name: "John", money: 1000, // for hint="string" toString() { return `{name: "${this.name}"}`; }, // for hint="number" or "default" valueOf() { return this.money; } }; alert(user); // toString -> {name: "John"} alert(+user); // valueOf -> 1000 alert(user + 500); // valueOf -> 1500
Wie wir sehen können, ist das Verhalten das gleiche wie im vorherigen Beispiel mit Symbol.toPrimitive
.
Oft wünschen wir uns einen einzigen „Catch-All“-Ort, an dem alle primitiven Konvertierungen abgewickelt werden. In diesem Fall können wir nur toString
wie folgt implementieren:
let user = { name: "John", toString() { return this.name; } }; alert(user); // toString -> John alert(user + 500); // toString -> John500
Wenn Symbol.toPrimitive
und valueOf
nicht vorhanden sind, übernimmt toString
alle primitiven Konvertierungen.
Bei allen Methoden zur Konvertierung von Grundelementen ist es wichtig zu wissen, dass sie nicht unbedingt das „angedeutete“ Grundelement zurückgeben.
Es gibt keine Kontrolle darüber, ob toString
genau eine Zeichenfolge zurückgibt oder ob die Symbol.toPrimitive
-Methode eine Zahl für den Hinweis "number"
zurückgibt.
Das einzig Zwingende: Diese Methoden müssen ein Grundelement zurückgeben, kein Objekt.
Aus historischen Gründen gibt es keinen Fehler, wenn toString
oder valueOf
ein Objekt zurückgibt, aber dieser Wert wird ignoriert (als ob die Methode nicht vorhanden wäre). Das liegt daran, dass es in JavaScript in der Antike kein gutes „Fehler“-Konzept gab.
Im Gegensatz dazu ist Symbol.toPrimitive
strenger, es muss ein Primitive zurückgeben, sonst kommt es zu einem Fehler.
Wie wir bereits wissen, führen viele Operatoren und Funktionen Typkonvertierungen durch, z. B. wandelt die Multiplikation *
Operanden in Zahlen um.
Wenn wir ein Objekt als Argument übergeben, gibt es zwei Berechnungsschritte:
Zum Beispiel:
let obj = { // toString handles all conversions in the absence of other methods toString() { return "2"; } }; alert(obj * 2); // 4, object converted to primitive "2", then multiplication made it a number
obj * 2
wandelt das Objekt zunächst in ein Primitiv um (das ist eine Zeichenfolge "2"
)."2" * 2
zu 2 * 2
(die Zeichenfolge wird in eine Zahl umgewandelt).Binary Plus verkettet in der gleichen Situation Zeichenfolgen, da es gerne eine Zeichenfolge akzeptiert:
let obj = { toString() { return "2"; } }; alert(obj + 2); // "22" ("2" + 2), conversion to primitive returned a string => concatenation
Die Objekt-zu-Primitiv-Konvertierung wird automatisch von vielen integrierten Funktionen und Operatoren aufgerufen, die ein Primitiv als Wert erwarten.
Es gibt 3 Arten (Hinweise) davon:
"string"
(für alert
und andere Vorgänge, die einen String benötigen)"number"
(für Mathematik)"default"
(wenige Operatoren, normalerweise implementieren Objekte es auf die gleiche Weise wie "number"
)Die Spezifikation beschreibt explizit, welcher Operator welchen Hinweis verwendet.
Der Konvertierungsalgorithmus ist:
obj[Symbol.toPrimitive](hint)
auf, wenn die Methode vorhanden ist."string"
istobj.toString()
oder obj.valueOf()
aufzurufen, was auch immer vorhanden ist."number"
oder "default"
ist.obj.valueOf()
oder obj.toString()
aufzurufen, was auch immer vorhanden ist.Alle diese Methoden müssen ein Grundelement zurückgeben, damit sie funktionieren (falls definiert).
In der Praxis reicht es oft aus, nur obj.toString()
als „Catch-All“-Methode für String-Konvertierungen zu implementieren, die zu Protokollierungs- oder Debugzwecken eine „für Menschen lesbare“ Darstellung eines Objekts zurückgeben soll.