Mengapa ++ [[]] [+ []] + [+ []] mengembalikan string “10”?

1658

Ini valid dan mengembalikan string "10"dalam JavaScript ( lebih banyak contoh di sini ):

console.log(++[[]][+[]]+[+[]])

Mengapa? Apa yang terjadi disini?

SapuSeven
sumber
446
Mulailah dengan memahami bahwa +[]melemparkan array kosong ke 0... lalu buang siang ...;)
deceze
10
Lihatlah wtfjs.com - ada beberapa hal seperti itu dengan explanatinos.
ThiefMaster
3
@menerima, di mana Anda belajar hal-hal semacam itu? Buku yang mana? Saya belajar JS dari MDN dan mereka tidak mengajarkan hal-hal ini
Siddharth Thevaril
6
@ SidharthThevaril Cara yang sama Anda lakukan: seseorang diposting di suatu tempat dan saya kebetulan membacanya.
tipuan

Jawaban:

2072

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 Auntuk 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, 1ditambahkan, 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:

  1. Biarkan expr menjadi hasil evaluasi UnaryExpression.

  2. Return ToNumber (GetValue (expr)).

ToNumber() mengatakan:

Obyek

Terapkan langkah-langkah berikut:

  1. Biarkan primValue menjadi ToPrimitive (argumen input, String string).

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

  1. Biarkan toString menjadi hasil dari memanggil metode [[Get]] internal objek O dengan argumen "toString".

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

15.4.4.2 Array.prototype.toString ()

Ketika metode toString dipanggil, langkah-langkah berikut diambil:

  1. Biarkan array menjadi hasil dari panggilan ToObject pada nilai ini.

  2. Biarkan func menjadi hasil dari memanggil metode internal [[Get]] dengan argumen "join".

  3. Jika IsCallable (func) salah, maka biarkan func menjadi metode bawaan bawaan Object.prototype.toString (15.2.4.2).

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

  1. Biarkan expr menjadi hasil evaluasi UnaryExpression.

  2. Return ToNumber (GetValue (expr)).

ToNumberdidefinisikan ""sebagai:

MV dari StringNumericLiteral ::: [kosong] adalah 0.

Jadi +"" === 0, dan karenanya +[] === 0.

pimvdb
sumber
8
@ Harper: Ini pemeriksa kesetaraan yang ketat, yaitu hanya mengembalikan truejika nilai dan jenisnya sama. 0 == ""kembali true(sama setelah konversi tipe), tapi 0 === ""ini false(tidak jenis yang sama).
pimvdb
41
Sebagian dari ini tidak benar. Ekspresi turun ke 1 + [0], bukan "1" + [0], karena ++operator awalan ( ) selalu mengembalikan nomor. Lihat bclary.com/2004/11/07/#a-11.4.4
Tim Down
6
@Tim Down: Anda sepenuhnya benar. Saya mencoba untuk memperbaiki ini, tetapi ketika mencoba melakukannya saya menemukan sesuatu yang lain. Saya tidak yakin bagaimana ini mungkin. ++[[]][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?
pimvdb
11
@ pimvdb: Saya cukup yakin masalahnya ada di PutValuepanggilan (dalam terminologi ES3, 8.7.2) dalam operasi awalan. PutValuemembutuhkan Referensi sedangkan []sebagai ekspresi sendiri tidak menghasilkan Referensi. Ekspresi yang berisi referensi variabel (katakanlah kita sebelumnya didefinisikan var a = []kemudian ++aberfungsi) 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.
Tim Down
13
@ pimvdb: Jadi setelah mengeksekusi var a = []; ++a, aadalah 1. Setelah mengeksekusi ++[[]][0], array yang dibuat oleh [[]]ekspresi sekarang hanya berisi angka 1 pada indeks 0. ++membutuhkan Referensi untuk melakukan ini.
Tim Down
124
++[[]][+[]] => 1 // [+[]] = [0], ++0 = 1
[+[]] => [0]

Kemudian kami memiliki rangkaian string

1+[0].toString() = 10
Shef
sumber
7
Bukankah lebih jelas untuk menulis ===daripada =>?
Mateen Ulhaq
61

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 0oleh ToNumber . Kami sekarang dapat mengganti 0untuk 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 0dan nilai ini bertambah dengan 1memberi kami nilai akhir 1. Nilai properti 0di array luar diperbarui ke 1dan 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.prototypeakan 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 ...

Tim Down
sumber
24

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"
renatoluna
sumber
13

Yang ini dievaluasi sama tapi sedikit lebih kecil

+!![]+''+(+[])
  • [] - adalah array yang dikonversi yang dikonversi menjadi 0 ketika Anda menambah atau mengurangi darinya, jadi karenanya + [] = 0
  • ! [] - dievaluasi menjadi false, jadi karenanya !! [] dievaluasi menjadi true
  • + !! [] - mengonversi true ke nilai numerik yang dievaluasi menjadi true, jadi dalam kasus ini 1
  • + '' - menambahkan string kosong ke ekspresi yang menyebabkan angka dikonversi menjadi string
  • + [] - mengevaluasi ke 0

jadi dievaluasi untuk

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

Jadi sekarang Anda mengerti, coba yang ini:

_=$=+[],++_+''+$
Vlad Shlosberg
sumber
Yah tidak masih mengevaluasi ke "10". Namun ini melakukannya dengan cara yang berbeda. Coba evaluasi ini di inspektur javascript seperti chrome atau sesuatu.
Vlad Shlosberg
_ = $ = + [], ++ _ + '' + $ -> _ = $ = 0, ++ _ + '' + $ -> _ = 0, $ = 0, ++ _ + '' + $ $ -> ++ 0 + '' + 0 -> 1 + '' + 0 -> '10' // Yei: v
LeagueOfJava
Yang ini mengevaluasi sama tetapi lebih kecil dari milik Anda:"10"
ADJenks
7

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

Eskat0n
sumber
4

Mungkin cara terpendek yang mungkin untuk mengevaluasi ekspresi menjadi "10" tanpa angka adalah:

+!+[] + [+[]] // "10"

-~[] + [+[]] // "10"

// ========== Penjelasan ========== \\

+!+[]: +[]Konversi ke 0. !0dikonversi ke true. +truedikonversi ke 1. -~[]= -(-1)yang adalah 1

[+[]]: +[]Konversi ke 0. [0]adalah larik dengan elemen tunggal 0.

Kemudian JS mengevaluasi 1 + [0], dengan demikian Number + Arrayekspresi. Kemudian spesifikasi ECMA berfungsi: +operator mengubah kedua operan menjadi string dengan memanggil toString()/valueOf()fungsi dari Objectprototipe 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 Objectshasil tambahan dalam NaN:

[] + []   //    ""
[1] + [2] //    "12"
{} + {}   //    NaN
{a:1} + {b:2}     //    NaN
[1, {}] + [2, {}] //    "1,[object Object]2,[object Object]"
AsyncMind
sumber
1
  1. Unary plus string yang diberikan dikonversi ke angka
  2. Operator kenaikan diberi konversi string dan kenaikan oleh 1
  3. [] == ''. String Kosong
  4. + '' atau + [] mengevaluasi 0.

    ++[[]][+[]]+[+[]] = 10 
    ++[''][0] + [0] : First part is gives zeroth element of the array which is empty string 
    1+0 
    10
Praveen Vedanth
sumber
1
Jawabannya membingungkan / membingungkan, TUNGGU salah. []adalah tidak setara dengan "". Pertama elemen diekstraksi, lalu dikonversi oleh ++.
PointedEars
1

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

Mengapa mengembalikan string "10"?

Alireza
sumber