Dlaczego 2 == [2] w JavaScript?

164

Niedawno odkryłem to 2 == [2]w JavaScript. Jak się okazuje, to dziwactwo ma kilka interesujących konsekwencji:

var a = [0, 1, 2, 3];
a[[2]] === a[2]; // this is true

Podobnie działa:

var a = { "abc" : 1 };
a[["abc"]] === a["abc"]; // this is also true

Co jeszcze dziwniejsze, to też działa:

[[[[[[[2]]]]]]] == 2; // this is true too! WTF?

Te zachowania wydają się spójne we wszystkich przeglądarkach.

Masz jakiś pomysł, dlaczego jest to funkcja języka?

Oto bardziej szalone konsekwencje tej „funkcji”:

[0] == false // true
if ([0]) { /* executes */ } // [0] is both true and false!

var a = [0];
a == a // true
a == !a // also true, WTF?

Te przykłady zostały znalezione przez sławę jimbojw http://jimbojw.com oraz walkingeyerobot .

Xavi
źródło

Odpowiedzi:

134

Możesz sprawdzić algorytm porównawczy w specyfikacji ECMA (odpowiednie sekcje ECMA-262, 3. wydanie dla twojego problemu: 11.9.3, 9.1, 8.6.2.6).

Jeśli przetłumaczysz zaangażowane abstrakcyjne algorytmy z powrotem na JS, to, co dzieje się podczas oceny, 2 == [2]jest zasadniczo takie:

2 === Number([2].valueOf().toString())

gdzie valueOf()for arrays zwraca samą tablicę, a reprezentacja ciągu w tablicy jednoelementowej jest reprezentacją ciągu pojedynczego elementu.

To wyjaśnia również trzeci przykład, ponieważ [[[[[[[2]]]]]]].toString()nadal jest tylko ciągiem 2.

Jak widać, w grę wchodzi sporo zakulisowej magii, dlatego generalnie używam tylko operatora ścisłej równości ===.

Pierwszy i drugi przykład są łatwiejsze do zrozumienia, ponieważ nazwy właściwości są zawsze ciągami, więc

a[[2]]

jest równa

a[[2].toString()]

co jest sprawiedliwe

a["2"]

Należy pamiętać, że nawet klucze numeryczne są traktowane jako nazwy właściwości (tj. Łańcuchy), zanim nastąpi jakakolwiek magia tablic.

Christoph
źródło
10

Dzieje się tak z powodu niejawnej konwersji typu ==operatora.

[2] jest konwertowane na Number to 2 w porównaniu z Number. Wypróbuj jednoargumentowy +operator na [2].

> +[2]
2
Chetan S
źródło
Inni mówią, że [2] jest konwertowane na łańcuch. +"2"jest również numerem 2.
dlamblin
1
Właściwie to nie jest takie proste. [2] jest konwertowane na ciąg znaków, byłoby bliżej, ale spójrz na ecma-international.org/ecma-262/5.1/#sec-11.9.3
neo
10
var a = [0, 1, 2, 3];
a[[2]] === a[2]; // this is true

Po prawej stronie równania mamy a [2], która zwraca typ liczbowy o wartości 2. Po lewej stronie najpierw tworzymy nową tablicę z jednym obiektem o wartości 2. Następnie wywołujemy a [( tablica jest tutaj)]. Nie jestem pewien, czy daje to ciąg, czy liczbę. 2 lub „2”. Najpierw weźmy wielkość liter. Uważam, że ["2"] utworzy nową zmienną i zwróci wartość null. null! == 2. Załóżmy więc, że faktycznie jest to niejawna konwersja na liczbę. a [2] zwróciłoby 2. 2 i 2 pasujące pod względem typu (więc === działa) i wartości. Myślę, że jest to niejawna konwersja tablicy na liczbę, ponieważ [wartość] oczekuje ciągu lub liczby. Wygląda na to, że liczba ma wyższy priorytet.

Na marginesie, zastanawiam się, kto decyduje o tym pierwszeństwie. Czy dlatego, że [2] ma liczbę jako pierwszą pozycję, więc jest konwertowana na liczbę? A może jest tak, że podczas przekazywania tablicy do [tablicy] próbuje najpierw przekształcić tablicę w liczbę, a następnie w łańcuch. Kto wie?

var a = { "abc" : 1 };
a[["abc"]] === a["abc"];

W tym przykładzie tworzysz obiekt o nazwie a z elementem o nazwie abc. Prawa strona równania jest dość prosta; jest odpowiednikiem a.abc. Zwraca to 1. Lewa strona najpierw tworzy dosłowną tablicę ["abc"]. Następnie wyszukujesz zmienną w obiekcie, przekazując nowo utworzoną tablicę. Ponieważ oczekuje ciągu, konwertuje tablicę na ciąg. To teraz daje ["abc"], które jest równe 1. 1 i 1 są tego samego typu (dlatego === działa) i mają taką samą wartość.

[[[[[[[2]]]]]]] == 2; 

To jest tylko niejawna konwersja. === nie zadziała w tej sytuacji, ponieważ występuje niezgodność typów.

Shawn
źródło
Odpowiedź na twoje pytanie o pierwszeństwo: ==dotyczy ToPrimitive()tablicy, która z kolei wywołuje swoją toString()metodę, więc to, co faktycznie porównujesz, to liczba 2do łańcucha "2"; Porównanie między łańcuchem a liczbą odbywa się poprzez konwersję łańcucha
Christoph
8

W tym ==przypadku Doug Crockford zaleca zawsze używanie ===. Nie wykonuje żadnej niejawnej konwersji typu.

W przykładach with ===niejawna konwersja typów jest wykonywana przed wywołaniem operatora równości.

Dan Hook
źródło
7
[0] == false // true
if ([0]) { /* executes */ } // [0] is both true and false!

To ciekawe, w rzeczywistości nie jest tak, że [0] jest zarówno prawdziwe, jak i fałszywe

[0] == true // false

Jest to zabawny sposób przetwarzania operatora if () w javascript.

Alexander Abramov
źródło
4
właściwie to ==działa w zabawny sposób ; jeśli użyjesz rzeczywistego, jawnego rzutowania (tj. Boolean([0])lub !![0]), przekonasz się, że w kontekstach logicznych [0]będzie to obliczane truetak, jak powinno: w JS każdy obiekt jest uważany zatrue
Christoph
6

Tablicę jednego elementu można traktować jak sam element.

Wynika to z pisania kaczkami. Ponieważ "2" == 2 == [2] i prawdopodobnie więcej.

Ólafur Waage
źródło
4
ponieważ nie pasują do typu. w pierwszym przykładzie lewa strona jest oceniana jako pierwsza i kończy się dopasowywaniem typu.
Shawn
8
Poza tym nie sądzę, żeby pisanie kaczkami było tutaj właściwym słowem. Jest to bardziej związane z niejawną konwersją typu wykonywaną przez ==operatora przed porównaniem.
Chetan S
14
nie ma to nic wspólnego z pisaniem typu kaczego, ale raczej ze słabym pisaniem, tj. niejawną konwersją typu
Christoph
@Chetan: co powiedział;)
Christoph
2
Co powiedzieli Chetan i Christoph.
Tim Down
3

Aby dodać trochę szczegółów do innych odpowiedzi ... porównując an Arraydo a Number, Javascript przekonwertuje Arrayz parseFloat(array). Możesz spróbować samemu w konsoli (np. Firebug lub Web Inspector), aby zobaczyć, na jakie różne Arraywartości zostaną przekonwertowane.

parseFloat([2]); // 2
parseFloat([2, 3]); // 2
parseFloat(['', 2]); // NaN

Dla Arrays parseFloatwykonuje operację na Arraypierwszym składniku, a resztę odrzuca.

Edycja: według szczegółów Christopha może się zdarzyć, że wewnętrznie używa dłuższej formy, ale wyniki są konsekwentnie identyczne parseFloat, więc zawsze możesz użyć parseFloat(array)skrótu, aby wiedzieć na pewno, w jaki sposób zostanie przekonwertowany.

bez powiek
źródło
2

Porównujesz 2 obiekty w każdym przypadku .. Nie używaj ==, jeśli myślisz o porównaniu, masz na myśli ===, a nie ==. == często daje szalone efekty. Szukaj dobrych części w języku :)

Jaseem
źródło
0

Wyjaśnienie do sekcji EDYCJA pytania:

Pierwszy przykład

[0] == false // true
if ([0]) { /* executes */ } // [0] is both true and false!

Pierwszy typ [0] do wartości pierwotnej, zgodnie z odpowiedzią Christopha powyżej, mamy „0” ( [0].valueOf().toString())

"0" == false

Teraz wpisz Boolean (false) na Number, a następnie String („0”) na Number

Number("0") == Number(false)
or  0 == 0 
so, [0] == false  // true

Jeśli chodzi o ifstwierdzenie, jeśli nie ma wyraźnego porównania w samym warunku if, warunek jest oceniany jako prawdziwy wartości.

Jest tylko 6 fałszywych wartości : false, null, undefined, 0, NaN i pusty ciąg „”. A wszystko, co nie jest wartością fałszywą, jest wartością zgodną z prawdą.

Ponieważ [0] nie jest fałszywą wartością, jest prawdziwą wartością, wynikiem ifinstrukcji jest prawda i ją wykonuje.


2. przykład

var a = [0];
a == a // true
a == !a // also true, WTF?

Ponownie wpisz rzutowanie wartości na prymitywne,

    a = a
or  [0].valueOf().toString() == [0].valueOf().toString()
or  "0" == "0" // true; same type, same value


a == !a
or  [0].valueOf().toString() == [0].valueOf().toString()
or  "0" == !"0"
or  "0" == false
or  Number("0") == Number(false)
or  0 = 0   // true
n4m31ess_c0d3r
źródło