Ini valid dan mengembalikan string "10"
dalam JavaScript ( lebih banyak contoh di sini ):
console.log(++[[]][+[]]+[+[]])
Mengapa? Apa yang terjadi disini?
Ini valid dan mengembalikan string "10"
dalam JavaScript ( lebih banyak contoh di sini ):
console.log(++[[]][+[]]+[+[]])
Mengapa? Apa yang terjadi disini?
Jawaban:
Jika kita membaginya, kekacauannya sama dengan:
++[[]][+[]]
+
[+[]]
Dalam JavaScript, memang benar begitu +[] === 0
. +
mengubah sesuatu menjadi angka, dan dalam hal ini akan turun ke +""
atau 0
(lihat detail spesifikasi di bawah).
Karena itu, kita dapat menyederhanakannya ( ++
didahulukan +
):
++[[]][0]
+
[0]
Karena [[]][0]
berarti: dapatkan elemen pertama dari [[]]
, memang benar bahwa:
[[]][0]
mengembalikan array batin ( []
). Karena referensi salah untuk mengatakan [[]][0] === []
, tetapi mari kita panggil array dalam A
untuk menghindari notasi yang salah.
++
sebelum operannya berarti "bertambah satu demi satu dan kembalikan hasil yang bertambah". Jadi ++[[]][0]
setara dengan Number(A) + 1
(atau +A + 1
).
Sekali lagi, kita dapat menyederhanakan kekacauan menjadi sesuatu yang lebih mudah dibaca. Mari kita ganti []
kembali untuk A
:
(+[] + 1)
+
[0]
Sebelum +[]
dapat memaksa array ke dalam angka 0
, ia harus dipaksa menjadi string terlebih dahulu, yaitu ""
, sekali lagi. Akhirnya, 1
ditambahkan, yang menghasilkan 1
.
(+[] + 1) === (+"" + 1)
(+"" + 1) === (0 + 1)
(0 + 1) === 1
Mari kita semakin menyederhanakannya:
1
+
[0]
Juga, ini benar dalam JavaScript:, [0] == "0"
karena menggabungkan array dengan satu elemen. Bergabung akan menggabungkan elemen-elemen yang dipisahkan oleh ,
. Dengan satu elemen, Anda dapat menyimpulkan bahwa logika ini akan menghasilkan elemen pertama itu sendiri.
Dalam hal ini, +
lihat dua operan: angka dan array. Sekarang mencoba untuk memaksa keduanya menjadi tipe yang sama. Pertama, array dipaksa ke dalam string "0"
, selanjutnya, nomor dipaksa ke dalam string ( "1"
). Jumlah +
String ===
String .
"1" + "0" === "10" // Yay!
Detail spesifikasi untuk +[]
:
Ini cukup membingungkan, tetapi untuk dilakukan +[]
, pertama itu sedang dikonversi ke string karena itulah yang +
mengatakan:
11.4.6 Operator + Unary
Operator + unary mengubah operan ke tipe Number.
Produksi UnaryExpression: + UnaryExpression dievaluasi sebagai berikut:
Biarkan expr menjadi hasil evaluasi UnaryExpression.
Return ToNumber (GetValue (expr)).
ToNumber()
mengatakan:
Obyek
Terapkan langkah-langkah berikut:
Biarkan primValue menjadi ToPrimitive (argumen input, String string).
Kembali ToString (primValue).
ToPrimitive()
mengatakan:
Obyek
Kembalikan nilai default untuk Objek. Nilai default suatu objek diambil dengan memanggil metode internal [[DefaultValue]] objek, melewati petunjuk opsional PreferredType. Perilaku metode internal [[DefaultValue]] ditentukan oleh spesifikasi ini untuk semua objek ECMAScript asli di 8.12.8.
[[DefaultValue]]
mengatakan:
8.12.8 [[Nilai Default]] (petunjuk)
Ketika metode internal [[DefaultValue]] O dipanggil dengan hint String, langkah-langkah berikut diambil:
Biarkan toString menjadi hasil dari memanggil metode [[Get]] internal objek O dengan argumen "toString".
Jika IsCallable (toString) benar maka,
Sebuah. Biarkan str menjadi hasil dari memanggil metode internal [[Panggil]] toString, dengan O sebagai nilai ini dan daftar argumen kosong.
b. Jika str adalah nilai primitif, kembalikan str.
The .toString
array mengatakan:
15.4.4.2 Array.prototype.toString ()
Ketika metode toString dipanggil, langkah-langkah berikut diambil:
Biarkan array menjadi hasil dari panggilan ToObject pada nilai ini.
Biarkan func menjadi hasil dari memanggil metode internal [[Get]] dengan argumen "join".
Jika IsCallable (func) salah, maka biarkan func menjadi metode bawaan bawaan Object.prototype.toString (15.2.4.2).
Kembalikan hasil memanggil metode internal [[Panggil]] dari func menyediakan array sebagai nilai ini dan daftar argumen kosong.
Jadi +[]
turun ke +""
, karena [].join() === ""
.
Sekali lagi, +
didefinisikan sebagai:
11.4.6 Operator + Unary
Operator + unary mengubah operan ke tipe Number.
Produksi UnaryExpression: + UnaryExpression dievaluasi sebagai berikut:
Biarkan expr menjadi hasil evaluasi UnaryExpression.
Return ToNumber (GetValue (expr)).
ToNumber
didefinisikan ""
sebagai:
MV dari StringNumericLiteral ::: [kosong] adalah 0.
Jadi +"" === 0
, dan karenanya +[] === 0
.
true
jika nilai dan jenisnya sama. 0 == ""
kembali true
(sama setelah konversi tipe), tapi 0 === ""
ini false
(tidak jenis yang sama).
1 + [0]
, bukan "1" + [0]
, karena ++
operator awalan ( ) selalu mengembalikan nomor. Lihat bclary.com/2004/11/07/#a-11.4.4
++[[]][0]
mengembalikan memang 1
, tetapi ++[]
melempar kesalahan. Ini luar biasa karena sepertinya ++[[]][0]
tidak mendidih ++[]
. Apakah Anda mungkin tahu mengapa ++[]
melempar kesalahan sedangkan ++[[]][0]
tidak?
PutValue
panggilan (dalam terminologi ES3, 8.7.2) dalam operasi awalan. PutValue
membutuhkan Referensi sedangkan []
sebagai ekspresi sendiri tidak menghasilkan Referensi. Ekspresi yang berisi referensi variabel (katakanlah kita sebelumnya didefinisikan var a = []
kemudian ++a
berfungsi) atau akses properti objek (seperti [[]][0]
) menghasilkan Referensi. Dalam istilah yang lebih sederhana, operator awalan tidak hanya menghasilkan nilai, tetapi juga membutuhkan tempat untuk meletakkan nilai itu.
var a = []; ++a
, a
adalah 1. Setelah mengeksekusi ++[[]][0]
, array yang dibuat oleh [[]]
ekspresi sekarang hanya berisi angka 1 pada indeks 0. ++
membutuhkan Referensi untuk melakukan ini.
++[[]][+[]] => 1 // [+[]] = [0], ++0 = 1
[+[]] => [0]
Kemudian kami memiliki rangkaian string
1+[0].toString() = 10
===
daripada =>
?
Berikut ini diadaptasi dari posting blog yang menjawab pertanyaan ini yang saya posting ketika pertanyaan ini masih ditutup. Tautan adalah ke (salinan HTML dari) spesifikasi ECMAScript 3, masih merupakan dasar untuk JavaScript di peramban web yang umum digunakan saat ini.
Pertama, sebuah komentar: ungkapan semacam ini tidak akan pernah muncul di lingkungan produksi (waras) apa pun dan hanya digunakan sebagai latihan dalam seberapa baik pembaca mengetahui tepi kotor JavaScript. Prinsip umum bahwa operator JavaScript yang secara implisit mengkonversi antar jenis berguna, seperti juga beberapa konversi umum, tetapi banyak detail dalam hal ini tidak.
Ekspresi ++[[]][+[]]+[+[]]
awalnya mungkin terlihat agak mengesankan dan tidak jelas, tetapi sebenarnya relatif mudah dipecah menjadi ekspresi yang terpisah. Di bawah ini saya hanya menambahkan tanda kurung untuk kejelasan; Saya dapat meyakinkan Anda bahwa mereka tidak mengubah apa pun, tetapi jika Anda ingin memverifikasi itu jangan ragu untuk membaca tentang operator pengelompokan . Jadi, ungkapan itu bisa ditulis dengan lebih jelas
( ++[[]][+[]] ) + ( [+[]] )
Dengan memecah ini, kita dapat menyederhanakan dengan mengamati yang +[]
mengevaluasi 0
. Untuk memuaskan diri Anda sendiri mengapa ini benar, periksa operator + unary dan ikuti jejak sedikit berliku yang berakhir dengan ToPrimitive mengubah array kosong menjadi string kosong, yang akhirnya dikonversi 0
oleh ToNumber . Kami sekarang dapat mengganti 0
untuk setiap instance dari +[]
:
( ++[[]][0] ) + [0]
Sudah lebih sederhana. Adapun ++[[]][0]
, itu adalah kombinasi dari operator kenaikan awalan ( ++
), array literal mendefinisikan array dengan elemen tunggal yang merupakan array kosong ( [[]]
) dan accessor properti ( [0]
) dipanggil pada array yang ditentukan oleh array literal.
Jadi, kita bisa menyederhanakan [[]][0]
menjadi adil []
dan sudah ++[]
, kan? Sebenarnya, ini bukan masalahnya karena mengevaluasi ++[]
melempar kesalahan, yang awalnya mungkin membingungkan. Namun, sedikit pemikiran tentang sifat ++
memperjelas: ini digunakan untuk meningkatkan variabel (misalnya ++i
) atau properti objek (misalnya ++obj.count
). Tidak hanya mengevaluasi ke suatu nilai, itu juga menyimpan nilai itu di suatu tempat. Dalam kasus ++[]
, tidak ada tempat untuk meletakkan nilai baru (apa pun itu) karena tidak ada referensi ke properti objek atau variabel untuk diperbarui. Dalam istilah tertentu, ini dicakup oleh operasi PutValue internal , yang disebut oleh operator kenaikan awalan.
Jadi, apa fungsinya ++[[]][0]
? Nah, dengan logika yang sama seperti +[]
, array batin dikonversi ke 0
dan nilai ini bertambah dengan 1
memberi kami nilai akhir 1
. Nilai properti 0
di array luar diperbarui ke 1
dan seluruh ekspresi dievaluasi 1
.
Ini meninggalkan kita
1 + [0]
... yang merupakan penggunaan sederhana dari operator penjumlahan . Kedua operan pertama dikonversi menjadi primitif dan jika salah satu nilai primitif adalah string, penggabungan string dilakukan, jika tidak penambahan numerik dilakukan. [0]
dikonversi ke "0"
, jadi string concatenation digunakan, menghasilkan "10"
.
Sebagai penutup akhir, sesuatu yang mungkin tidak segera terlihat adalah bahwa mengesampingkan salah satu toString()
atau valueOf()
metode Array.prototype
akan mengubah hasil ekspresi, karena keduanya diperiksa dan digunakan jika ada saat mengubah suatu objek menjadi nilai primitif. Misalnya berikut ini
Array.prototype.toString = function() {
return "foo";
};
++[[]][+[]]+[+[]]
... menghasilkan "NaNfoo"
. Mengapa ini terjadi dibiarkan sebagai latihan bagi pembaca ...
Mari kita membuatnya sederhana:
++[[]][+[]]+[+[]] = "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"
Yang ini dievaluasi sama tapi sedikit lebih kecil
+!![]+''+(+[])
jadi dievaluasi untuk
+(true) + '' + (0)
1 + '' + 0
"10"
Jadi sekarang Anda mengerti, coba yang ini:
_=$=+[],++_+''+$
"10"
+ [] mengevaluasi ke 0 [...] lalu menjumlahkan (+ operasi) dengan apa pun yang mengubah konten array menjadi representasi string yang terdiri dari elemen yang digabungkan dengan koma.
Apa pun selain mengambil indeks array (memiliki prioritas lebih dari + operasi) adalah ordinal dan tidak ada yang menarik.
Mungkin cara terpendek yang mungkin untuk mengevaluasi ekspresi menjadi "10" tanpa angka adalah:
+!+[] + [+[]]
// "10"
-~[] + [+[]]
// "10"
// ========== Penjelasan ========== \\
+!+[]
: +[]
Konversi ke 0. !0
dikonversi ke true
. +true
dikonversi ke 1.
-~[]
= -(-1)
yang adalah 1
[+[]]
: +[]
Konversi ke 0. [0]
adalah larik dengan elemen tunggal 0.
Kemudian JS mengevaluasi 1 + [0]
, dengan demikian Number + Array
ekspresi. Kemudian spesifikasi ECMA berfungsi: +
operator mengubah kedua operan menjadi string dengan memanggil toString()/valueOf()
fungsi dari Object
prototipe dasar . Ini beroperasi sebagai fungsi aditif jika kedua operan dari ekspresi hanya angka. Kuncinya adalah bahwa array dengan mudah mengubah elemen-elemen mereka menjadi representasi string gabungan.
Beberapa contoh:
1 + {} // "1[object Object]"
1 + [] // "1"
1 + new Date() // "1Wed Jun 19 2013 12:13:25 GMT+0400 (Caucasus Standard Time)"
Ada pengecualian yang baik bahwa dua Objects
hasil tambahan dalam NaN
:
[] + [] // ""
[1] + [2] // "12"
{} + {} // NaN
{a:1} + {b:2} // NaN
[1, {}] + [2, {}] // "1,[object Object]2,[object Object]"
+ '' atau + [] mengevaluasi 0.
++[[]][+[]]+[+[]] = 10
++[''][0] + [0] : First part is gives zeroth element of the array which is empty string
1+0
10
[]
adalah tidak setara dengan ""
. Pertama elemen diekstraksi, lalu dikonversi oleh ++
.
Langkah demi langkah itu, +
ubah nilainya menjadi angka dan jika Anda menambahkan ke array kosong +[]
... karena kosong dan sama dengan 0
, itu akan
Jadi dari sana, sekarang lihat kode Anda, itu ++[[]][+[]]+[+[]]
...
Dan ada plus di antara mereka ++[[]][+[]]
+[+[]]
Jadi ini [+[]]
akan kembali [0]
karena mereka memiliki array kosong yang akan dikonversi ke 0
dalam array lain ...
Jadi seperti bayangkan, nilai pertama adalah array 2 dimensi dengan satu array di dalam ... sehingga [[]][+[]]
akan sama dengan [[]][0]
yang akan kembali []
...
Dan pada akhirnya ++
mengubahnya dan meningkatkannya menjadi 1
...
Jadi bisa dibayangkan, 1
+ "0"
akan "10"
...
+[]
melemparkan array kosong ke0
... lalu buang siang ...;)