Dlaczego ++ [[]] [+ []] + [+ []] zwraca ciąg „10”?


1657

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?


446
Zacznij od zrozumienia, że +[]rzuca pustą tablicę na 0... a potem
zmarnuj


10
Spójrz na wtfjs.com - ma sporo takich rzeczy z wyjaśnianiem.
ThiefMaster

3
@deceze, gdzie uczysz się tego rodzaju rzeczy? Które książki? Uczę się JS z MDN i nie uczą tych rzeczy
Siddharth Thevaril,

6
@ SiddharthThevaril Tak samo jak przed chwilą: ktoś gdzieś o tym napisał, a ja go przeczytałem.
deceze

Odpowiedzi:


2070

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ą, Aaby 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 1dodaje 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:

  1. Niech wyrażenie będzie wynikiem oceny UnaryExpression.

  2. Return ToNumber (GetValue (expr)).

ToNumber() mówi:

Obiekt

Wykonaj następujące kroki:

  1. Niech primValue będzie ToPrimitive (argument wejściowy, wskazówka String).

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

  1. Niech toString będzie wynikiem wywołania wewnętrznej metody [[Get]] obiektu O z argumentem „toString”.

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

.toStringTablicy mówi:

15.4.4.2 Array.prototype.toString ()

Po wywołaniu metody toString podejmowane są następujące kroki:

  1. Niech tablica będzie wynikiem wywołania ToObject dla tej wartości.

  2. Niech func będzie wynikiem wywołania wewnętrznej metody tablicy [[Get]] z argumentem „join”.

  3. Jeśli IsCallable (func) ma wartość false, niech func będzie standardową wbudowaną metodą Object.prototype.toString (15.2.4.2).

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

  1. Niech wyrażenie będzie wynikiem oceny UnaryExpression.

  2. Return ToNumber (GetValue (expr)).

ToNumberjest zdefiniowany ""jako:

Wartość MV StringNumericLiteral ::: [pusta] wynosi 0.

A więc +"" === 0i tak +[] === 0.


8
@harper: Jest to ścisły moduł sprawdzający równość, tzn. zwraca tylko, truejeś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).
pimvdb

41
Część tego jest nieprawidłowa. Wyrażenie sprowadza się do 1 + [0], nie "1" + [0], ponieważ ++operator prefiksu ( ) zawsze zwraca liczbę. Patrz bclary.com/2004/11/07/#a-11.4.4
Tim Down

6
@ Tim Down: masz całkowitą rację. Próbuję to naprawić, ale próbując to zrobić, znalazłem coś innego. Nie jestem pewien, jak to możliwe. ++[[]][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?
pimvdb

11
@pimvdb: Jestem prawie pewien, że problem polega na PutValuewywołaniu (w terminologii ES3, 8.7.2) w operacji prefiksu. PutValuewymaga 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 ++adział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ć.
Tim Down

13
@pimvdb: Czyli po wykonaniu var a = []; ++a, ato 1. Po wykonaniu ++[[]][0]tablica tworzona przez [[]]wyrażenie jest teraz zawiera tylko numer 1 w indeksie 0. ++wymaga Reference to zrobić.
Tim Down

123
++[[]][+[]] => 1 // [+[]] = [0], ++0 = 1
[+[]] => [0]

Następnie mamy konkatenację strun

1+[0].toString() = 10

7
Czy pisanie nie byłoby łatwiejsze ===niż =>?
Mateen Ulhaq,

61

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 0przez ToNumber . Możemy teraz zastąpić 0każ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, 0a wartość ta jest zwiększana o, 1aby dać nam końcową wartość 1. Wartość właściwości 0w zewnętrznej tablicy jest aktualizowana do, 1a 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.prototypezmieni 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 ...


24

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"

13

Ten ocenia to samo, ale nieco mniej

+!![]+''+(+[])
  • [] - to tablica jest konwertowana, która jest konwertowana na 0 po dodaniu lub odjęciu, więc + [] = 0
  • ! [] - ocenia na false, a więc !! [] ocenia na true
  • + !! [] - konwertuje wartość true na wartość liczbową, która zwraca wartość true, więc w tym przypadku 1
  • + '' - dołącza pusty ciąg do wyrażenia powodując konwersję liczby na ciąg
  • + [] - ocenia na 0

tak ocenia

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

Więc teraz masz to, spróbuj tego:

_=$=+[],++_+''+$

Cóż, nie, nadal ma wartość „10”. Robi to jednak inaczej. Spróbuj to ocenić w inspektorze javascript, takim jak chrome czy coś takiego.
Vlad Shlosberg,

_ = $ = + [], ++ _ + '' + $ -> _ = $ = 0, ++ _ + '' + $ -> _ = 0, $ = 0, ++ _ + '' + $ -> ++ 0 + '' + 0 -> 1 + '' + 0 -> '10' // Yei: v
LeagueOfJava

Ten ocenia to samo, ale jest jeszcze mniejszy niż twój:"10"
ADJenks

7

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


4

Być może najkrótszymi możliwymi sposobami oceny wyrażenia na „10” bez cyfr są:

+!+[] + [+[]] // „10”

-~[] + [+[]] // „10”

// ========== Objaśnienie ========== \\

+!+[]: +[]Konwertuje na 0. !0konwertuje na true. +truekonwertuje na 1. -~[]= -(-1)co jest 1

[+[]]: +[]Konwertuje na 0. [0]to tablica z pojedynczym elementem 0.

Następnie JS ocenia 1 + [0], zatem Number + Arraywyraż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 Objectspowoduje dwa dodania NaN:

[] + []   //    ""
[1] + [2] //    "12"
{} + {}   //    NaN
{a:1} + {b:2}     //    NaN
[1, {}] + [2, {}] //    "1,[object Object]2,[object Object]"

1
  1. Unary plus podany ciąg jest konwertowany na liczbę
  2. Operator przyrostu otrzymuje ciąg znaków i konwertuje o 1
  3. [] == ''. Pusta struna
  4. + '' lub + [] ocenia 0.

    ++[[]][+[]]+[+[]] = 10 
    ++[''][0] + [0] : First part is gives zeroth element of the array which is empty string 
    1+0 
    10

1
Odpowiedź jest mylona / myląca, IOW źle. nie[] jest równoważne z . Najpierw element jest wyodrębniany, a następnie konwertowany przez . ""++
PointedEars,

1

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"...

Dlaczego zwraca ciąg „10”?

Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.