Jest to poprawne i zwraca ciąg znaków "10"
w JavaScript ( więcej przykładów tutaj ):
console.log(++[[]][+[]]+[+[]])
Dlaczego? Co tu się dzieje?
Jest to poprawne i zwraca ciąg znaków "10"
w JavaScript ( więcej przykładów tutaj ):
console.log(++[[]][+[]]+[+[]])
Dlaczego? Co tu się dzieje?
Odpowiedzi:
Jeśli podzielimy to, bałagan będzie równy:
++[[]][+[]]
+
[+[]]
W JavaScript jest to prawda +[] === 0
. +
konwertuje coś na liczbę, w tym przypadku sprowadza się do +""
lub 0
(patrz szczegóły specyfikacji poniżej).
Dlatego możemy to uprościć ( ++
ma przewagę nad +
):
++[[]][0]
+
[0]
Ponieważ [[]][0]
oznacza: pobierz pierwszy element [[]]
, to prawda, że:
[[]][0]
zwraca tablicę wewnętrzną ( []
). Z powodu odniesień nie można powiedzieć [[]][0] === []
, ale wywołajmy tablicę wewnętrzną, A
aby uniknąć niewłaściwej notacji.
++
przed operandem oznacza „przyrost o jeden i zwrócenie przyrostu wyniku”. ++[[]][0]
Jest więc równoważne z Number(A) + 1
(lub +A + 1
).
Ponownie możemy uprościć bałagan w coś bardziej czytelnego. Zastąpmy z []
powrotem za A
:
(+[] + 1)
+
[0]
Zanim będzie +[]
można zmusić tablicę do liczby 0
, najpierw trzeba ją przymusić do łańcucha, czyli ""
znowu. Wreszcie 1
dodaje się, co powoduje 1
.
(+[] + 1) === (+"" + 1)
(+"" + 1) === (0 + 1)
(0 + 1) === 1
Uprośćmy to jeszcze bardziej:
1
+
[0]
Jest to również prawdą w JavaScript: [0] == "0"
ponieważ łączy tablicę z jednym elementem. Łączenie połączy elementy oddzielone ,
. Za pomocą jednego elementu można wywnioskować, że ta logika spowoduje powstanie samego pierwszego elementu.
W tym przypadku +
widzi dwa operandy: liczbę i tablicę. Teraz próbuje zmusić oba do tego samego typu. Najpierw tablica jest wymuszana na ciąg "0"
, następnie liczba jest wymuszana na ciąg ( "1"
). Liczba +
Ciąg ===
Ciąg .
"1" + "0" === "10" // Yay!
Szczegóły specyfikacji dla +[]
:
Jest to dość labirynt, ale aby to zrobić +[]
, najpierw jest konwertowany na ciąg, ponieważ tak +
mówi:
11.4.6 Unary + Operator
Operator unary + konwertuje operand na typ liczby.
Produkcja UnaryExpression: + UnaryExpression jest oceniany w następujący sposób:
Niech wyrażenie będzie wynikiem oceny UnaryExpression.
Return ToNumber (GetValue (expr)).
ToNumber()
mówi:
Obiekt
Wykonaj następujące kroki:
Niech primValue będzie ToPrimitive (argument wejściowy, wskazówka String).
Return ToString (primValue).
ToPrimitive()
mówi:
Obiekt
Zwraca domyślną wartość dla obiektu. Domyślna wartość obiektu jest pobierana poprzez wywołanie wewnętrznej metody [[DefaultValue]] obiektu, przekazując opcjonalną wskazówkę PreferredType. Zachowanie metody wewnętrznej [[DefaultValue]] jest zdefiniowane w tej specyfikacji dla wszystkich rodzimych obiektów ECMAScript w 8.12.8.
[[DefaultValue]]
mówi:
8.12.8 [[DefaultValue]] (wskazówka)
Gdy wywoływana jest wewnętrzna metoda [[DefaultValue]] O z podpowiedzią String, podejmowane są następujące kroki:
Niech toString będzie wynikiem wywołania wewnętrznej metody [[Get]] obiektu O z argumentem „toString”.
Jeśli IsCallable (toString) jest prawdziwe, to
za. Niech str będzie wynikiem wywołania wewnętrznej metody [[Call]] toString, z O jako tą wartością i pustą listą argumentów.
b. Jeśli str jest wartością pierwotną, zwróć str.
.toString
Tablicy mówi:
15.4.4.2 Array.prototype.toString ()
Po wywołaniu metody toString podejmowane są następujące kroki:
Niech tablica będzie wynikiem wywołania ToObject dla tej wartości.
Niech func będzie wynikiem wywołania wewnętrznej metody tablicy [[Get]] z argumentem „join”.
Jeśli IsCallable (func) ma wartość false, niech func będzie standardową wbudowaną metodą Object.prototype.toString (15.2.4.2).
Zwraca wynik wywołania wewnętrznej metody [[Call]] func zapewniającej tablicę jako tę wartość i pustą listę argumentów.
Tak +[]
sprowadza się do tego +""
, ponieważ [].join() === ""
.
Ponownie, +
jest to zdefiniowane jako:
11.4.6 Unary + Operator
Operator unary + konwertuje operand na typ liczby.
Produkcja UnaryExpression: + UnaryExpression jest oceniany w następujący sposób:
Niech wyrażenie będzie wynikiem oceny UnaryExpression.
Return ToNumber (GetValue (expr)).
ToNumber
jest zdefiniowany ""
jako:
Wartość MV StringNumericLiteral ::: [pusta] wynosi 0.
A więc +"" === 0
i tak +[] === 0
.
true
jeśli zarówno wartość, jak i typ są takie same. 0 == ""
zwraca true
(to samo po konwersji typu), ale 0 === ""
jest false
(nie tych samych typów).
1 + [0]
, nie "1" + [0]
, ponieważ ++
operator prefiksu ( ) zawsze zwraca liczbę. Patrz bclary.com/2004/11/07/#a-11.4.4
++[[]][0]
zwraca rzeczywiście 1
, ale ++[]
generuje błąd. Jest to niezwykłe, ponieważ wygląda na to, że ++[[]][0]
sprowadza się do ++[]
. Czy może masz pojęcie, dlaczego ++[]
zgłasza błąd, a ++[[]][0]
nie robi?
PutValue
wywołaniu (w terminologii ES3, 8.7.2) w operacji prefiksu. PutValue
wymaga odwołania, podczas gdy []
samo wyrażenie nie tworzy odniesienia. Wyrażenie zawierające zmienne odwołanie (powiedzmy, że wcześniej zdefiniowaliśmy, var a = []
a następnie ++a
działa) lub dostęp do właściwości obiektu (np. [[]][0]
) Tworzy odwołanie. Mówiąc prościej, operator prefiksu nie tylko tworzy wartość, ale także musi ją gdzieś umieścić.
var a = []; ++a
, a
to 1. Po wykonaniu ++[[]][0]
tablica tworzona przez [[]]
wyrażenie jest teraz zawiera tylko numer 1 w indeksie 0. ++
wymaga Reference to zrobić.
++[[]][+[]] => 1 // [+[]] = [0], ++0 = 1
[+[]] => [0]
Następnie mamy konkatenację strun
1+[0].toString() = 10
===
niż =>
?
Poniższy fragment został zaadaptowany z postu na blogu, w którym odpowiedziałem na to pytanie, które opublikowałem, gdy to pytanie było jeszcze zamknięte. Odsyłacze są (kopią HTML) specyfikacji ECMAScript 3, będącej nadal podstawą dla JavaScript w dzisiejszych powszechnie używanych przeglądarkach internetowych.
Po pierwsze, komentarz: ten rodzaj wyrażenia nigdy nie pojawi się w żadnym (rozsądnym) środowisku produkcyjnym i służy jedynie jako ćwiczenie tego, jak dobrze czytelnik zna brudne krawędzie JavaScript. Ogólna zasada, że operatorzy JavaScript domyślnie dokonują konwersji między typami, jest użyteczna, podobnie jak niektóre typowe konwersje, ale większość szczegółów w tym przypadku nie jest.
Wyrażenie ++[[]][+[]]+[+[]]
może początkowo wyglądać narzucająco i niejasno, ale w rzeczywistości jest stosunkowo łatwe do podziału na osobne wyrażenia. Poniżej po prostu dodałem nawiasy dla jasności; Zapewniam cię, że nic nie zmieniają, ale jeśli chcesz to sprawdzić, możesz przeczytać o operatorze grupowania . Wyrażenie można więc wyraźniej zapisać jako
( ++[[]][+[]] ) + ( [+[]] )
Rozbijając to, możemy uprościć, obserwując, który +[]
ocenia 0
. Aby zadowolić siebie, dlaczego tak jest, sprawdź operator jednoargumentowy + , a następnie lekko kręty szlak, który kończy się ToPrimitive przekształcenie pustą tablicę na pusty ciąg, który następnie przekształca się w końcu 0
przez ToNumber . Możemy teraz zastąpić 0
każdą instancję +[]
:
( ++[[]][0] ) + [0]
Już prościej. Jeśli chodzi o to ++[[]][0]
, jest to kombinacja operatora przyrostu prefiksu ( ++
), literału tablicowego definiującego tablicę z pojedynczym elementem, który sam jest pustą tablicą ( [[]]
) i accessorem właściwości ( [0]
) wywoływanym na tablicę zdefiniowaną przez literał tablicowy.
Tak więc, możemy uprościć [[]][0]
po prostu []
i mamy ++[]
, prawda? W rzeczywistości tak nie jest, ponieważ ocena ++[]
powoduje błąd, który początkowo może wydawać się mylący. Jednak mała refleksja na temat natury ++
wyjaśnia: jest używana do zwiększania zmiennej (np. ++i
) Lub właściwości obiektu (np ++obj.count
.). Nie tylko ocenia wartość, ale także gdzieś ją zapisuje. W przypadku ++[]
, nie ma gdzie umieścić nowej wartości (cokolwiek by to nie było), ponieważ nie ma odniesienia do właściwości obiektu lub zmiennej do aktualizacji. Mówiąc konkretnie , jest to objęte wewnętrzną operacją PutValue , która jest wywoływana przez operator inkrementacji prefiksu.
Co więc robi ++[[]][0]
? Cóż, według podobnej logiki +[]
, tablica wewnętrzna jest konwertowana na, 0
a wartość ta jest zwiększana o, 1
aby dać nam końcową wartość 1
. Wartość właściwości 0
w zewnętrznej tablicy jest aktualizowana do, 1
a całe wyrażenie ocenia na 1
.
To nas zostawia
1 + [0]
... co jest prostym zastosowaniem operatora dodawania . Oba operandy są najpierw konwertowane na prymitywy, a jeśli jedna z wartości pierwotnych jest łańcuchem, wykonywana jest konkatenacja łańcucha, w przeciwnym razie przeprowadzane jest dodawanie liczbowe. [0]
konwertuje na "0"
, więc używana jest konkatenacja łańcuchów, tworząc "10"
.
Na koniec, czymś, co może nie być od razu widoczne, jest to, że przesłonięcie jednej z metod toString()
lub valueOf()
metody Array.prototype
zmieni wynik wyrażenia, ponieważ obie są sprawdzane i używane, jeśli występują podczas przekształcania obiektu w pierwotną wartość. Na przykład następujące
Array.prototype.toString = function() {
return "foo";
};
++[[]][+[]]+[+[]]
... produkuje "NaNfoo"
. Dlaczego tak się dzieje, pozostaje zadaniem czytelnika ...
Uprośćmy to:
++[[]][+[]]+[+[]] = "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"
Ten ocenia to samo, ale nieco mniej
+!![]+''+(+[])
tak ocenia
+(true) + '' + (0)
1 + '' + 0
"10"
Więc teraz masz to, spróbuj tego:
_=$=+[],++_+''+$
"10"
+ [] ocenia na 0 [...], a następnie sumuje (operacja +) z czymkolwiek konwertuje zawartość tablicy na reprezentację ciągu składającą się z elementów połączonych przecinkiem.
Cokolwiek innego, jak pobranie indeksu tablicy (ma większy priorytet niż operacja +), jest porządkowe i nie jest niczym interesującym.
Być może najkrótszymi możliwymi sposobami oceny wyrażenia na „10” bez cyfr są:
+!+[] + [+[]]
// „10”
-~[] + [+[]]
// „10”
// ========== Objaśnienie ========== \\
+!+[]
: +[]
Konwertuje na 0. !0
konwertuje na true
. +true
konwertuje na 1.
-~[]
= -(-1)
co jest 1
[+[]]
: +[]
Konwertuje na 0. [0]
to tablica z pojedynczym elementem 0.
Następnie JS ocenia 1 + [0]
, zatem Number + Array
wyrażenie. Następnie działa specyfikacja ECMA: +
operator konwertuje oba operandy na ciąg, wywołując toString()/valueOf()
funkcje z bazyObject
prototypu. Działa jako funkcja addytywna, jeśli oba operandy wyrażenia są tylko liczbami. Sztuka polega na tym, że tablice łatwo przekształcają swoje elementy w połączoną reprezentację łańcucha.
Kilka przykładów:
1 + {} // "1[object Object]"
1 + [] // "1"
1 + new Date() // "1Wed Jun 19 2013 12:13:25 GMT+0400 (Caucasus Standard Time)"
Jest ładny wyjątek, który Objects
powoduje dwa dodania NaN
:
[] + [] // ""
[1] + [2] // "12"
{} + {} // NaN
{a:1} + {b:2} // NaN
[1, {}] + [2, {}] // "1,[object Object]2,[object Object]"
+ '' lub + [] ocenia 0.
++[[]][+[]]+[+[]] = 10
++[''][0] + [0] : First part is gives zeroth element of the array which is empty string
1+0
10
[]
jest równoważne z . Najpierw element jest wyodrębniany, a następnie konwertowany przez . ""
++
Krok po kroku +
zamień wartość na liczbę, a jeśli dodasz do pustej tablicy +[]
... ponieważ jest pusta i równa się0
, będzie
Stamtąd, spójrz teraz na swój kod, to jest ++[[]][+[]]+[+[]]
...
I jest między nimi ++[[]][+[]]
plus[+[]]
Tak więc [+[]]
powrócą, [0]
ponieważ mają pustą tablicę, na którą można przekonwertować0
wewnątrz drugiej tablicy ...
Jak więc sobie wyobrazić, pierwszą wartością jest dwuwymiarowa tablica z jedną tablicą wewnątrz ... więc [[]][+[]]
będzie równa, [[]][0]
która zwróci []
...
Na koniec ++
przekonwertuj go i zwiększ do1
...
Więc możesz sobie wyobrazić, 1
+ "0"
będzie "10"
...
+[]
rzuca pustą tablicę na0
... a potem