Dies ist gültig und gibt die Zeichenfolge "10"
in JavaScript zurück ( weitere Beispiele hier ):
console.log(++[[]][+[]]+[+[]])
Warum? Was passiert hier?
Dies ist gültig und gibt die Zeichenfolge "10"
in JavaScript zurück ( weitere Beispiele hier ):
console.log(++[[]][+[]]+[+[]])
Warum? Was passiert hier?
Antworten:
Wenn wir es aufteilen, ist das Chaos gleich:
++[[]][+[]]
+
[+[]]
In JavaScript ist das wahr +[] === 0
. +
wandelt etwas in eine Zahl um, und in diesem Fall kommt es auf +""
oder an 0
(siehe Spezifikationsdetails unten).
Daher können wir es vereinfachen ( ++
hat Vorrang vor +
):
++[[]][0]
+
[0]
Denn [[]][0]
bedeutet: Holen Sie sich das erste Element von [[]]
, es ist wahr, dass:
[[]][0]
Gibt das innere Array ( []
) zurück. Aufgrund von Referenzen ist es falsch zu sagen [[]][0] === []
, aber nennen wir das innere Array A
, um die falsche Notation zu vermeiden.
++
vor seinem Operanden bedeutet "um eins erhöhen und das inkrementierte Ergebnis zurückgeben". Ist ++[[]][0]
also gleichbedeutend mit Number(A) + 1
(oder +A + 1
).
Auch hier können wir das Durcheinander in etwas Lesbareres vereinfachen. Lassen Sie uns zurück ersetzen []
für A
:
(+[] + 1)
+
[0]
Bevor +[]
das Array in die Zahl 0
gezwungen werden kann , muss es zuerst in eine Zeichenfolge gezwungen werden, was ""
wiederum der Fall ist . Schließlich 1
wird hinzugefügt, was zu führt 1
.
(+[] + 1) === (+"" + 1)
(+"" + 1) === (0 + 1)
(0 + 1) === 1
Vereinfachen wir es noch weiter:
1
+
[0]
Dies gilt auch für JavaScript : [0] == "0"
, da ein Array mit einem Element verbunden wird. Durch das Verknüpfen werden die durch getrennten Elemente verkettet ,
. Mit einem Element können Sie ableiten, dass diese Logik zum ersten Element selbst führt.
In diesem Fall werden +
zwei Operanden angezeigt: eine Zahl und ein Array. Es wird jetzt versucht, die beiden zum gleichen Typ zu zwingen. Zuerst wird das Array in die Zeichenfolge gezwungen "0"
, dann wird die Zahl in eine Zeichenfolge ( "1"
) gezwungen . Number +
String ===
String .
"1" + "0" === "10" // Yay!
Spezifikationsdetails für +[]
:
Dies ist ein ziemliches Labyrinth, aber zu tun +[]
, zuerst wird es in eine Zeichenfolge konvertiert, weil das +
sagt:
11.4.6 Unary + Operator
Der Operator unary + konvertiert seinen Operanden in den Typ Number.
Die Produktion UnaryExpression: + UnaryExpression wird wie folgt ausgewertet:
Sei expr das Ergebnis der Auswertung von UnaryExpression.
Return ToNumber (GetValue (Ausdruck)).
ToNumber()
sagt:
Objekt
Führen Sie die folgenden Schritte aus:
Sei primValue ToPrimitive (Eingabeargument, Hinweis String).
Zurück zu ToString (primValue).
ToPrimitive()
sagt:
Objekt
Gibt einen Standardwert für das Objekt zurück. Der Standardwert eines Objekts wird durch Aufrufen der internen Methode [[DefaultValue]] des Objekts abgerufen, wobei der optionale Hinweis PreferredType übergeben wird. Das Verhalten der internen Methode [[DefaultValue]] wird durch diese Spezifikation für alle nativen ECMAScript-Objekte in 8.12.8 definiert.
[[DefaultValue]]
sagt:
8.12.8 [[DefaultValue]] (Hinweis)
Wenn die interne Methode [[DefaultValue]] von O mit dem Hinweis String aufgerufen wird, werden die folgenden Schritte ausgeführt:
Sei toString das Ergebnis des Aufrufs der internen Methode [[Get]] von Objekt O mit dem Argument "toString".
Wenn IsCallable (toString) wahr ist, dann
ein. Sei str das Ergebnis des Aufrufs der internen Methode [[Call]] von toString mit O als diesem Wert und einer leeren Argumentliste.
b. Wenn str ein primitiver Wert ist, geben Sie str zurück.
Das .toString
eines Arrays sagt:
15.4.4.2 Array.prototype.toString ()
Wenn die toString-Methode aufgerufen wird, werden die folgenden Schritte ausgeführt:
Das Array sei das Ergebnis des Aufrufs von ToObject für diesen Wert.
Sei func das Ergebnis des Aufrufs der internen Array-Methode [[Get]] mit dem Argument "join".
Wenn IsCallable (func) false ist, sei func die integrierte Standardmethode Object.prototype.toString (15.2.4.2).
Gibt das Ergebnis des Aufrufs der internen Methode [[Call]] von func zurück, die ein Array als diesen Wert und eine leere Argumentliste bereitstellt.
Also +[]
kommt es darauf an +""
, weil [].join() === ""
.
Wieder ist das +
definiert als:
11.4.6 Unary + Operator
Der Operator unary + konvertiert seinen Operanden in den Typ Number.
Die Produktion UnaryExpression: + UnaryExpression wird wie folgt ausgewertet:
Sei expr das Ergebnis der Auswertung von UnaryExpression.
Return ToNumber (GetValue (Ausdruck)).
ToNumber
ist definiert ""
als:
Das MV von StringNumericLiteral ::: [leer] ist 0.
So +"" === 0
und so +[] === 0
.
true
wenn sowohl Wert als auch Typ identisch sind. 0 == ""
Erträge true
(gleich nach Typumwandlung), aber 0 === ""
ist false
(nicht gleiche Typen).
1 + [0]
, nicht "1" + [0]
, weil der ++
Operator prefix ( ) immer eine Zahl zurückgibt. Siehe bclary.com/2004/11/07/#a-11.4.4
++[[]][0]
kehrt zwar zurück 1
, ++[]
wirft aber einen Fehler aus. Das ist bemerkenswert, weil es so aussieht, als würde ++[[]][0]
es darauf hinauslaufen ++[]
. Haben Sie vielleicht eine Idee, warum ++[]
ein Fehler ausgelöst ++[[]][0]
wird, während dies nicht der Fall ist?
PutValue
Aufruf (in der ES3-Terminologie 8.7.2) in der Präfixoperation liegt. PutValue
erfordert eine Referenz, während []
ein Ausdruck für sich genommen keine Referenz erzeugt. Ein Ausdruck, der eine Variablenreferenz enthält (sagen wir, wir haben zuvor definiert, funktioniert var a = []
dann ++a
) oder den Eigenschaftszugriff auf ein Objekt (z. B. [[]][0]
), erzeugt eine Referenz. Einfacher ausgedrückt erzeugt der Präfixoperator nicht nur einen Wert, sondern benötigt auch einen Platz, um diesen Wert zu setzen.
var a = []; ++a
, a
ist 1. Nach der Ausführung ++[[]][0]
wird die Anordnung durch den erstellte [[]]
Ausdruck enthält jetzt nur die Nummer 1 bei Index 0 ++
erfordert eine Referenz , dies zu tun.
++[[]][+[]] => 1 // [+[]] = [0], ++0 = 1
[+[]] => [0]
Dann haben wir eine String-Verkettung
1+[0].toString() = 10
===
als =>
?
Das Folgende ist aus einem Blog-Beitrag übernommen , der diese Frage beantwortet, die ich gepostet habe, als diese Frage noch geschlossen war. Links verweisen auf (eine HTML-Kopie von) der ECMAScript 3-Spezifikation, die in den heute häufig verwendeten Webbrowsern immer noch die Basis für JavaScript darstellt.
Zunächst ein Kommentar: Diese Art von Ausdruck wird in keiner (vernünftigen) Produktionsumgebung auftauchen und ist nur als Übung dafür nützlich, wie gut der Leser die schmutzigen Kanten von JavaScript kennt. Das allgemeine Prinzip, dass JavaScript-Operatoren implizit zwischen Typen konvertieren, ist nützlich, ebenso wie einige der gängigen Konvertierungen, viele Details in diesem Fall jedoch nicht.
Der Ausdruck ++[[]][+[]]+[+[]]
mag anfangs eher imposant und undurchsichtig aussehen, ist aber tatsächlich relativ einfach in separate Ausdrücke zu zerlegen. Im Folgenden habe ich aus Gründen der Übersichtlichkeit einfach Klammern hinzugefügt. Ich kann Ihnen versichern, dass sie nichts ändern, aber wenn Sie dies überprüfen möchten, können Sie sich über den Gruppierungsoperator informieren . Der Ausdruck kann also klarer geschrieben werden als
( ++[[]][+[]] ) + ( [+[]] )
Wenn wir dies aufschlüsseln, können wir es vereinfachen, indem wir beobachten, dass es +[]
ausgewertet wird 0
. Um sich davon zu überzeugen , warum dies der Fall ist, überprüfen Sie den Operator unary + und folgen Sie dem leicht gewundenen Pfad, der dazu führt, dass ToPrimitive das leere Array in eine leere Zeichenfolge konvertiert, die dann 0
von ToNumber schließlich konvertiert wird . Wir können jetzt 0
jede Instanz von ersetzen +[]
:
( ++[[]][0] ) + [0]
Schon einfacher. Dies ++[[]][0]
ist eine Kombination aus dem Präfix-Inkrement-Operator ( ++
), einem Array-Literal , das ein Array mit einem einzelnen Element definiert, das selbst ein leeres Array ( [[]]
) ist, und einem Eigenschafts-Accessor ( [0]
), der für das durch das Array-Literal definierte Array aufgerufen wird.
Also können wir [[]][0]
es einfach vereinfachen []
und wir haben es ++[]
, richtig? In der Tat ist dies nicht der Fall, da die Auswertung ++[]
einen Fehler auslöst, der zunächst verwirrend erscheinen kann. Ein kleiner Gedanke über die Art von ++
macht dies jedoch deutlich: Es wird verwendet, um eine Variable (z. B. ++i
) oder eine Objekteigenschaft (z ++obj.count
. B. ) zu erhöhen . Es wird nicht nur zu einem Wert ausgewertet, sondern dieser Wert wird auch irgendwo gespeichert. Im Fall von ++[]
kann der neue Wert nirgendwo platziert werden (was auch immer er sein mag), da kein Verweis auf eine zu aktualisierende Objekteigenschaft oder Variable vorhanden ist. In technischen Begriffen wird dies durch die interne PutValue- Operation abgedeckt , die vom Präfix-Inkrement-Operator aufgerufen wird.
Also, was macht ++[[]][0]
dann? Nun, nach ähnlicher Logik wie +[]
wird das innere Array konvertiert 0
und dieser Wert wird um erhöht 1
, um einen endgültigen Wert von zu erhalten 1
. Der Wert der Eigenschaft 0
im äußeren Array wird aktualisiert 1
und der gesamte Ausdruck wird ausgewertet 1
.
Das lässt uns mit
1 + [0]
... was eine einfache Verwendung des Additionsoperators ist . Beide Operanden werden zuerst in Grundelemente konvertiert. Wenn einer der Grundelemente eine Zeichenfolge ist, wird eine Zeichenfolgenverkettung durchgeführt, andernfalls wird eine numerische Addition durchgeführt. [0]
konvertiert nach "0"
, so dass die Verkettung von Zeichenfolgen verwendet wird, wodurch erzeugt wird "10"
.
Abgesehen davon ist etwas, das möglicherweise nicht sofort ersichtlich ist, dass das Überschreiben einer toString()
oder valueOf()
mehrerer Methoden Array.prototype
das Ergebnis des Ausdrucks ändert, da beide überprüft und verwendet werden, wenn sie vorhanden sind, wenn ein Objekt in einen primitiven Wert konvertiert wird. Zum Beispiel das Folgende
Array.prototype.toString = function() {
return "foo";
};
++[[]][+[]]+[+[]]
... produziert "NaNfoo"
. Warum dies geschieht, bleibt dem Leser als Übung überlassen ...
Machen wir es einfach:
++[[]][+[]]+[+[]] = "10"
var a = [[]][+[]];
var b = [+[]];
// so a == [] and b == [0]
++a;
// then a == 1 and b is still that array [0]
// when you sum the var a and an array, it will sum b as a string just like that:
1 + "0" = "10"
Dieser wird gleich bewertet, aber etwas kleiner
+!![]+''+(+[])
so wird ausgewertet
+(true) + '' + (0)
1 + '' + 0
"10"
Also jetzt haben Sie das, versuchen Sie dieses:
_=$=+[],++_+''+$
"10"
+ [] wird zu 0 [...] ausgewertet und dann durch Summieren (+ Operation) mit irgendetwas konvertiert der Array-Inhalt in seine Zeichenfolgendarstellung, die aus mit Komma verbundenen Elementen besteht.
Alles andere wie das Aufnehmen des Array-Index (mit größerer Priorität als + Operation) ist ordinal und nichts Interessantes.
Möglicherweise sind die kürzesten Möglichkeiten, einen Ausdruck ohne Ziffern in "10" zu bewerten, folgende:
+!+[] + [+[]]
// "10"
-~[] + [+[]]
// "10"
// ========== Erklärung ========== \\
+!+[]
: +[]
Konvertiert in 0. !0
Konvertiert in true
. +true
konvertiert zu 1.
-~[]
= -(-1)
was 1 ist
[+[]]
: +[]
Konvertiert in 0. [0]
ist ein Array mit einem einzelnen Element 0.
Dann JS wertet den 1 + [0]
damit Number + Array
Ausdruck. Dann funktioniert die ECMA-Spezifikation: Der +
Operator konvertiert beide Operanden in einen String, indem er die toString()/valueOf()
Funktionen von der Basis aus aufruftObject
. Es funktioniert als additive Funktion, wenn beide Operanden eines Ausdrucks nur Zahlen sind. Der Trick besteht darin, dass Arrays ihre Elemente leicht in eine verkettete Zeichenfolgendarstellung konvertieren.
Einige Beispiele:
1 + {} // "1[object Object]"
1 + [] // "1"
1 + new Date() // "1Wed Jun 19 2013 12:13:25 GMT+0400 (Caucasus Standard Time)"
Es gibt eine schöne Ausnahme, zu der zwei Objects
Additionen führen NaN
:
[] + [] // ""
[1] + [2] // "12"
{} + {} // NaN
{a:1} + {b:2} // NaN
[1, {}] + [2, {}] // "1,[object Object]2,[object Object]"
+ '' oder + [] ergibt 0.
++[[]][+[]]+[+[]] = 10
++[''][0] + [0] : First part is gives zeroth element of the array which is empty string
1+0
10
[]
ist nicht gleichbedeutend mit ""
. Zuerst wird das Element extrahiert und dann von konvertiert ++
.
Verwandeln Sie den +
Wert Schritt für Schritt in eine Zahl, und wenn Sie ein leeres Array hinzufügen +[]
... da es leer ist und gleich ist, 0
wird es
Schauen Sie sich von dort aus Ihren Code an, es ist ++[[]][+[]]+[+[]]
...
Und es gibt und zwischen ihnen ++[[]][+[]]
+[+[]]
Diese [+[]]
kehren also zurück, [0]
da sie ein leeres Array haben, das 0
in das andere Array konvertiert wird ...
So wie ich vorstellen, der erste Wert ist ein 2-dimensionales Array mit einem Array innerhalb ... so [[]][+[]]
gleich sein wird, [[]][0]
die die Rückkehr wird []
...
Und am Ende ++
konvertieren Sie es und erhöhen Sie es auf 1
...
Sie können sich also vorstellen, 1
+ "0"
wird "10"
...
+[]
ein leeres Array0