Warum gibt ++ [[]] [+ []] + [+ []] die Zeichenfolge "10" zurück?

1657

Dies ist gültig und gibt die Zeichenfolge "10"in JavaScript zurück ( weitere Beispiele hier ):

console.log(++[[]][+[]]+[+[]])

Warum? Was passiert hier?

SapuSeven
quelle
446
Beginnen Sie mit dem Verständnis, dass +[]ein leeres Array 0
geworfen wird,
10
Schauen Sie sich wtfjs.com an - es gibt einige solche Dinge mit Erklärungen.
ThiefMaster
3
@deceze, wo lernst du solche Sachen? Welche Bücher? Ich lerne JS von MDN und sie lehren diese Dinge nicht
Siddharth Thevaril
6
@SiddharthThevaril So wie du es gerade getan hast: Jemand hat irgendwo darüber geschrieben und ich habe es zufällig gelesen.
Täuschung

Antworten:

2071

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 0gezwungen werden kann , muss es zuerst in eine Zeichenfolge gezwungen werden, was ""wiederum der Fall ist . Schließlich 1wird 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:

  1. Sei expr das Ergebnis der Auswertung von UnaryExpression.

  2. Return ToNumber (GetValue (Ausdruck)).

ToNumber() sagt:

Objekt

Führen Sie die folgenden Schritte aus:

  1. Sei primValue ToPrimitive (Eingabeargument, Hinweis String).

  2. 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:

  1. Sei toString das Ergebnis des Aufrufs der internen Methode [[Get]] von Objekt O mit dem Argument "toString".

  2. 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 .toStringeines Arrays sagt:

15.4.4.2 Array.prototype.toString ()

Wenn die toString-Methode aufgerufen wird, werden die folgenden Schritte ausgeführt:

  1. Das Array sei das Ergebnis des Aufrufs von ToObject für diesen Wert.

  2. Sei func das Ergebnis des Aufrufs der internen Array-Methode [[Get]] mit dem Argument "join".

  3. Wenn IsCallable (func) false ist, sei func die integrierte Standardmethode Object.prototype.toString (15.2.4.2).

  4. 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:

  1. Sei expr das Ergebnis der Auswertung von UnaryExpression.

  2. Return ToNumber (GetValue (Ausdruck)).

ToNumberist definiert ""als:

Das MV von StringNumericLiteral ::: [leer] ist 0.

So +"" === 0und so +[] === 0.

pimvdb
quelle
8
@harper: Dies ist die strikte Gleichheitsprüfung, dh sie wird nur zurückgegeben, truewenn sowohl Wert als auch Typ identisch sind. 0 == ""Erträge true(gleich nach Typumwandlung), aber 0 === ""ist false(nicht gleiche Typen).
Pimvdb
41
Ein Teil davon ist nicht korrekt. Der Ausdruck läuft darauf hinaus 1 + [0], nicht "1" + [0], weil der ++Operator prefix ( ) immer eine Zahl zurückgibt. Siehe bclary.com/2004/11/07/#a-11.4.4
Tim Down
6
@ Tim Down: Du bist völlig richtig. Ich versuche das zu korrigieren, aber als ich das versuchte, fand ich etwas anderes. Ich bin mir nicht sicher, wie das möglich ist. ++[[]][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?
Pimvdb
11
@pimvdb: Ich bin mir ziemlich sicher, dass das Problem im PutValueAufruf (in der ES3-Terminologie 8.7.2) in der Präfixoperation liegt. PutValueerfordert 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.
Tim Down
13
@pimvdb: Also nach der Ausführung var a = []; ++a, aist 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.
Tim Down
123
++[[]][+[]] => 1 // [+[]] = [0], ++0 = 1
[+[]] => [0]

Dann haben wir eine String-Verkettung

1+[0].toString() = 10
Shef
quelle
7
Wäre es nicht klarer zu schreiben ===als =>?
Mateen Ulhaq
61

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 0von ToNumber schließlich konvertiert wird . Wir können jetzt 0jede 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 0und dieser Wert wird um erhöht 1, um einen endgültigen Wert von zu erhalten 1. Der Wert der Eigenschaft 0im äußeren Array wird aktualisiert 1und 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.prototypedas 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 ...

Tim Down
quelle
24

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"
Renatoluna
quelle
13

Dieser wird gleich bewertet, aber etwas kleiner

+!![]+''+(+[])
  • [] - ist ein Array, das konvertiert wird und beim Addieren oder Subtrahieren in 0 konvertiert wird. Daher ist + [] = 0
  • ! [] - wird als falsch ausgewertet, daher wird !! [] als wahr ausgewertet
  • + !! [] - konvertiert das wahre in einen numerischen Wert, der als wahr ausgewertet wird, also in diesem Fall 1
  • + '' - Hängt eine leere Zeichenfolge an den Ausdruck an, wodurch die Zahl in eine Zeichenfolge konvertiert wird
  • + [] - ergibt 0

so wird ausgewertet

+(true) + '' + (0)
1 + '' + 0
"10"

Also jetzt haben Sie das, versuchen Sie dieses:

_=$=+[],++_+''+$
Vlad Shlosberg
quelle
Nun nein, es ergibt immer noch "10". Dies geschieht jedoch anders. Versuchen Sie dies in einem Javascript-Inspektor wie Chrome oder so zu bewerten.
Vlad Shlosberg
_ = $ = + [], ++ _ + '' + $ -> _ = $ = 0, ++ _ + '' + $ -> _ = 0, $ = 0, ++ _ + '' + $ -> ++ 0 + '' + 0 -> 1 + '' + 0 -> '10' // Yei: v
LeagueOfJava
Dieser wird gleich bewertet, ist aber noch kleiner als Ihrer:"10"
ADJenks
7

+ [] 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.

Eskat0n
quelle
4

Möglicherweise sind die kürzesten Möglichkeiten, einen Ausdruck ohne Ziffern in "10" zu bewerten, folgende:

+!+[] + [+[]] // "10"

-~[] + [+[]] // "10"

// ========== Erklärung ========== \\

+!+[]: +[]Konvertiert in 0. !0Konvertiert in true. +truekonvertiert 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 + ArrayAusdruck. 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 ObjectsAdditionen führen NaN:

[] + []   //    ""
[1] + [2] //    "12"
{} + {}   //    NaN
{a:1} + {b:2}     //    NaN
[1, {}] + [2, {}] //    "1,[object Object]2,[object Object]"
AsyncMind
quelle
1
  1. Unary plus gegebene Zeichenfolge wird in Zahl konvertiert
  2. Inkrementierungsoperator bei gegebener Zeichenfolge konvertiert und erhöht um 1
  3. [] == ''. Leerer String
  4. + '' oder + [] ergibt 0.

    ++[[]][+[]]+[+[]] = 10 
    ++[''][0] + [0] : First part is gives zeroth element of the array which is empty string 
    1+0 
    10
Praveen Vedanth
quelle
1
Die Antwort ist verwirrt / verwirrend, IOW falsch. []ist nicht gleichbedeutend mit "". Zuerst wird das Element extrahiert und dann von konvertiert ++.
PointedEars
1

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, 0wird 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 0in 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"...

Warum wird die Zeichenfolge "10" zurückgegeben?

Alireza
quelle