Mengapa 2 == [2] dalam JavaScript?

164

Saya baru-baru menemukan itu 2 == [2]di JavaScript. Ternyata, kekhasan ini memiliki beberapa konsekuensi menarik:

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

Demikian pula, karya-karya berikut ini:

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

Bahkan lebih aneh lagi, ini juga berfungsi:

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

Perilaku ini tampaknya konsisten di semua browser.

Adakah yang tahu mengapa ini fitur bahasa?

Berikut adalah konsekuensi lebih gila dari "fitur" ini:

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

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

Contoh-contoh ini ditemukan oleh ketenaran jimbojw http://jimbojw.com serta walkingeyerobot .

Xavi
sumber

Jawaban:

134

Anda dapat mencari algoritme perbandingan dalam spesifikasi ECMA (bagian yang relevan dari ECMA-262, edisi ke-3 untuk masalah Anda: 11.9.3, 9.1, 8.6.2.6).

Jika Anda menerjemahkan kembali algoritma abstrak yang terlibat ke JS, apa yang terjadi ketika mengevaluasi 2 == [2]pada dasarnya adalah ini:

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

dimana valueOf()untuk array mengembalikan array itu sendiri dan representasi string dari array satu elemen adalah representasi string dari elemen tunggal.

Ini juga menjelaskan contoh ketiga karena [[[[[[[2]]]]]]].toString()masih hanya string 2.

Seperti yang Anda lihat, ada cukup banyak keajaiban di belakang layar yang terlibat, itulah sebabnya saya umumnya hanya menggunakan operator kesetaraan yang ketat ===.

Contoh pertama dan kedua lebih mudah diikuti karena nama properti selalu berupa string, jadi

a[[2]]

setara dengan

a[[2].toString()]

yang mana saja

a["2"]

Perlu diingat bahwa bahkan tombol numerik diperlakukan sebagai nama properti (yaitu string) sebelum keajaiban array terjadi.

Christoph
sumber
10

Itu karena konversi tipe implisit dari ==operator.

[2] dikonversi menjadi Angka 2 jika dibandingkan dengan Angka. Coba +operator unary pada [2].

> +[2]
2
Chetan S
sumber
Yang lain mengatakan [2] dikonversi menjadi string. +"2"juga nomor 2.
dlamblin
1
Sebenarnya, itu tidak mudah. [2] dikonversi menjadi string akan lebih dekat tetapi lihat di 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

Di sisi kanan persamaan, kita memiliki a [2], yang mengembalikan tipe angka dengan nilai 2. Di sebelah kiri, kita pertama-tama membuat array baru dengan objek tunggal 2. Kemudian kita memanggil sebuah [( array ada di sini)]. Saya tidak yakin apakah ini dievaluasi menjadi string atau angka. 2, atau "2". Mari kita ambil kasing string terlebih dahulu. Saya percaya ["2"] akan membuat variabel baru dan mengembalikan nol. null! == 2. Jadi mari kita asumsikan itu sebenarnya secara implisit mengkonversi ke angka. a [2] akan mengembalikan 2. 2 dan 2 cocok dengan tipe (jadi === berfungsi) dan nilainya. Saya pikir itu secara implisit mengkonversi array ke angka karena [nilai] mengharapkan string atau angka. Sepertinya angka lebih diutamakan.

Di samping catatan, saya bertanya-tanya siapa yang menentukan prioritas itu. Apakah karena [2] memiliki nomor sebagai item pertama, sehingga dikonversi ke nomor? Atau apakah ketika mengirimkan array ke [array] ia mencoba mengubah array menjadi angka terlebih dahulu, kemudian string. Siapa tahu?

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

Dalam contoh ini, Anda membuat objek yang disebut dengan anggota yang disebut abc. Sisi kanan persamaan itu cukup sederhana; itu setara dengan a.abc. Ini mengembalikan 1. Sisi kiri pertama menciptakan array literal ["abc"]. Anda kemudian mencari variabel pada objek dengan mengirimkan array yang baru dibuat. Karena ini mengharapkan string, itu mengubah array menjadi string. Ini sekarang mengevaluasi ke ["abc"], yang sama dengan 1. 1 dan 1 adalah tipe yang sama (itulah sebabnya === bekerja) dan nilainya sama.

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

Ini hanya konversi implisit. === tidak akan berfungsi dalam situasi ini karena ada tipe ketidakcocokan.

Shawn
sumber
Jawaban untuk pertanyaan Anda tentang prioritas: ==berlaku ToPrimitive()untuk array, yang pada gilirannya memanggil toString()metode, jadi apa yang Anda benar-benar bandingkan adalah angka 2ke string "2"; perbandingan antara string dan angka dilakukan dengan mengubah string
Christoph
8

Untuk ==kasus ini, inilah sebabnya Doug Crockford merekomendasikan untuk selalu menggunakan ===. Itu tidak melakukan konversi tipe implisit.

Untuk contoh dengan ===, konversi tipe implisit dilakukan sebelum operator kesetaraan dipanggil.

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

Itu menarik, bukan bahwa [0] benar dan salah, sebenarnya

[0] == true // false

Ini adalah cara lucu javascript dalam memproses if () operator.

Alexander Abramov
sumber
4
sebenarnya, ini cara ==kerja yang lucu ; jika Anda menggunakan eksplisit cast yang sebenarnya (yaitu Boolean([0])atau !![0]), Anda akan menemukan bahwa [0]akan mengevaluasi ke truedalam konteks boolean sebagaimana mestinya: di JS, objek apapun dianggaptrue
Christoph
6

Array satu item dapat diperlakukan sebagai item itu sendiri.

Ini karena mengetik bebek. Karena "2" == 2 == [2] dan mungkin lebih.

Ólafur Waage
sumber
4
karena mereka tidak cocok dengan tipe. dalam contoh pertama, sisi kiri dievaluasi terlebih dahulu, dan mereka berakhir sesuai jenis.
Shawn
8
Juga, saya tidak berpikir mengetik bebek adalah kata yang tepat di sini. Ini lebih berkaitan dengan konversi tipe implisit yang dilakukan oleh ==operator sebelum membandingkan.
Chetan S
14
ini tidak ada hubungannya dengan mengetik bebek melainkan dengan mengetik lemah, yaitu konversi tipe implisit
Christoph
@ Chetan: apa yang dia katakan;)
Christoph
2
Apa yang dikatakan Chetan dan Christoph.
Tim Down
3

Untuk menambahkan sedikit detail ke jawaban lain ... saat membandingkan suatu Arraydengan Number, Javascript akan mengonversi Arraydengan parseFloat(array). Anda dapat mencobanya sendiri di konsol (mis. Firebug atau Web Inspector) untuk melihat Arraynilai-nilai berbeda apa yang dapat dikonversi.

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

For Arrays, parseFloatmelakukan operasi pada Arrayanggota pertama, dan membuang sisanya.

Sunting: Per detail Christoph, mungkin menggunakan formulir yang lebih panjang secara internal, tetapi hasilnya secara konsisten identik parseFloat, sehingga Anda selalu dapat menggunakan parseFloat(array)istilah singkat untuk mengetahui dengan pasti bagaimana itu akan dikonversi.

kelopak mata
sumber
2

Anda membandingkan 2 objek dalam setiap kasus .. Jangan gunakan ==, jika Anda berpikir tentang perbandingan, Anda memiliki === dalam pikiran dan bukan ==. == sering dapat memberikan efek gila. Cari bagian yang bagus dalam bahasa ini :)

Jaseem
sumber
0

Penjelasan untuk bagian EDIT dari pertanyaan:

Contoh 1

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

Typecast pertama [0] ke nilai primitif sesuai jawaban Christoph di atas, kami memiliki "0" ( [0].valueOf().toString())

"0" == false

Sekarang, ketikkan Boolean (false) to Number dan kemudian String ("0") ke Number

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

Adapun ifpernyataan, jika tidak ada perbandingan eksplisit dalam kondisi if itu sendiri, kondisi mengevaluasi nilai-nilai kebenaran .

Hanya ada 6 nilai falsy : false, null, undefined, 0, NaN, dan string kosong "". Dan segala sesuatu yang bukan nilai palsu adalah nilai kebenaran.

Karena [0] bukan nilai palsu, itu adalah nilai ifkebenaran , pernyataan dievaluasi menjadi true & mengeksekusi pernyataan.


Contoh ke-2

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

Sekali lagi ketik casting nilai ke primitif,

    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
sumber