The 'Wat' berbicara untuk CodeMash 2012 pada dasarnya menunjukkan sebuah kebiasaan aneh beberapa dengan Ruby dan JavaScript.
Saya telah membuat JSFiddle dari hasil di http://jsfiddle.net/fe479/9/ .
Perilaku khusus untuk JavaScript (karena saya tidak tahu Ruby) tercantum di bawah ini.
Saya menemukan di JSFiddle bahwa beberapa hasil saya tidak sesuai dengan yang ada di video, dan saya tidak yakin mengapa. Namun saya ingin tahu bagaimana penanganan JavaScript di belakang layar dalam setiap kasus.
Empty Array + Empty Array
[] + []
result:
<Empty String>
Saya cukup ingin tahu tentang +
operator ketika digunakan dengan array dalam JavaScript. Ini cocok dengan hasil video.
Empty Array + Object
[] + {}
result:
[Object]
Ini cocok dengan hasil video. Apa yang terjadi di sini? Mengapa ini sebuah objek? Apa yang dilakukan +
operator?
Object + Empty Array
{} + []
result:
[Object]
Ini tidak cocok dengan video. Video menunjukkan bahwa hasilnya adalah 0, sedangkan saya mendapatkan [Objek].
Object + Object
{} + {}
result:
[Object][Object]
Ini juga tidak cocok dengan video, dan bagaimana cara menghasilkan variabel menghasilkan dua objek? Mungkin JSFiddle saya salah.
Array(16).join("wat" - 1)
result:
NaNNaNNaNNaNNaNNaNNaNNaNNaNNaNNaNNaNNaNNaNNaNNaN
Melakukan wat + 1 menghasilkan wat1wat1wat1wat1
...
Saya menduga ini hanya perilaku langsung yang mencoba mengurangi angka dari hasil string di NaN.
sumber
Array(16).join("wat" - 1) + " Batman!"
{} + {}
.Jawaban:
Berikut daftar penjelasan untuk hasil yang Anda lihat (dan seharusnya dilihat). Referensi yang saya gunakan berasal dari standar ECMA-262 .
[] + []
Saat menggunakan operator tambahan, operan kiri dan kanan dikonversi menjadi primitif terlebih dahulu ( §11.6.1 ). Seperti pada §9.1 , mengonversi objek (dalam hal ini sebuah array) menjadi primitif mengembalikan nilai defaultnya, yang untuk objek dengan
toString()
metode yang valid adalah hasil dari pemanggilanobject.toString()
( §8.12.8 ). Untuk array, ini sama dengan memanggilarray.join()
( §15.4.4.2 ). Bergabung dengan array kosong menghasilkan string kosong, jadi langkah # 7 dari operator tambahan mengembalikan rangkaian dua string kosong, yang merupakan string kosong.[] + {}
Mirip dengan
[] + []
, kedua operan dikonversi menjadi primitif terlebih dahulu. Untuk "Objek objek" (§15.2), ini lagi merupakan hasil dari pemanggilanobject.toString()
, yang untuk objek non-null, non-tidak terdefinisi adalah"[object Object]"
( §15.2.4.2 ).{} + []
Di
{}
sini tidak diuraikan sebagai objek, melainkan sebagai blok kosong ( §12.1 , setidaknya selama Anda tidak memaksa pernyataan itu menjadi ekspresi, tetapi lebih lanjut tentang itu nanti). Nilai pengembalian blok kosong adalah kosong, sehingga hasil dari pernyataan itu sama dengan+[]
.+
Operator unary ( §11.4.6 ) kembaliToNumber(ToPrimitive(operand))
. Seperti yang sudah kita ketahui,ToPrimitive([])
adalah string kosong, dan menurut §9.3.1 ,ToNumber("")
adalah 0.{} + {}
Mirip dengan kasus sebelumnya, yang pertama
{}
diuraikan sebagai blok dengan nilai pengembalian kosong. Sekali lagi,+{}
adalah sama denganToNumber(ToPrimitive({}))
, danToPrimitive({})
yang"[object Object]"
(lihat[] + {}
). Jadi untuk mendapatkan hasil+{}
, kita harus menerapkannyaToNumber
pada string"[object Object]"
. Ketika mengikuti langkah-langkah dari §9.3.1 , kita mendapatkanNaN
hasilnya:Array(16).join("wat" - 1)
Sesuai §15.4.1.1 dan §15.4.2.2 ,
Array(16)
buat array baru dengan panjang 16. Untuk mendapatkan nilai argumen yang akan digabungkan, §11.6.2 langkah # 5 dan # 6 menunjukkan bahwa kita harus mengubah kedua operan menjadi nomor menggunakanToNumber
.ToNumber(1)
hanya 1 ( §9.3 ), sedangkanToNumber("wat")
sekali lagiNaN
sesuai §9.3.1 . Mengikuti langkah 7 dari §11.6.2 , §11.6.3 menentukan ituJadi argumennya
Array(16).join
adalahNaN
. Mengikuti §15.4.4.5 (Array.prototype.join
), kita harus memanggilToString
argumen, yaitu"NaN"
( §9.8.1 ):Mengikuti langkah 10 pada §15.4.4.5 , kami mendapatkan 15 pengulangan dari rangkaian
"NaN"
dan string kosong, yang sama dengan hasil yang Anda lihat. Saat menggunakan"wat" + 1
alih-alih"wat" - 1
sebagai argumen, operator tambahan mengonversi1
ke string alih-alih mengonversi"wat"
ke nomor, sehingga secara efektif memanggilArray(16).join("wat1")
.Mengenai mengapa Anda melihat hasil yang berbeda untuk
{} + []
kasus ini: Saat menggunakannya sebagai argumen fungsi, Anda memaksakan pernyataan itu menjadi ExpressionStatement , yang membuatnya tidak mungkin untuk menguraikan{}
sebagai blok kosong, jadi alih-alih diuraikan sebagai objek kosong harfiah.sumber
[]+1
cukup banyak mengikuti logika yang sama seperti[]+[]
, hanya dengan1.toString()
operan rhs. Untuk[]-1
melihat penjelasan"wat"-1
pada poin 5. Ingat ituToNumber(ToPrimitive([]))
adalah 0 (poin 3).Ini lebih merupakan komentar daripada jawaban, tetapi untuk beberapa alasan saya tidak dapat mengomentari pertanyaan Anda. Saya ingin memperbaiki kode JSFiddle Anda. Namun, saya memposting ini di Hacker News dan seseorang menyarankan agar saya memposting ulang di sini.
Masalah dalam kode JSFiddle adalah
({})
(membuka kurung di dalam tanda kurung) tidak sama dengan{}
(membuka kurung sebagai awal baris kode). Jadi, ketika Anda mengetik,out({} + [])
Anda memaksa{}
untuk menjadi sesuatu yang bukan saat Anda mengetik{} + []
. Ini adalah bagian dari 'wat'-ness keseluruhan Javascript.Ide dasarnya adalah JavaScript sederhana yang ingin memungkinkan kedua bentuk ini:
Untuk melakukannya, dua interpretasi dibuat dari brace pembuka: 1. tidak diperlukan dan 2. dapat muncul di mana saja .
Ini langkah yang salah. Kode nyata tidak memiliki tanda kurung pembuka yang muncul di tengah-tengah dari mana, dan kode nyata juga cenderung lebih rapuh ketika menggunakan bentuk pertama daripada yang kedua. (Sekitar sebulan sekali di pekerjaan terakhir saya, saya akan dipanggil ke meja rekan kerja ketika modifikasi mereka pada kode saya tidak berfungsi, dan masalahnya adalah mereka menambahkan baris ke "jika" tanpa menambahkan keriting Saya akhirnya hanya mengadopsi kebiasaan bahwa kurung kurawal selalu diperlukan, bahkan ketika Anda hanya menulis satu baris.)
Untungnya dalam banyak kasus eval () akan mereplikasi wat-ness penuh JavaScript. Kode JSFiddle harus dibaca:
[Juga itu adalah pertama kalinya saya menulis dokumen.writeln dalam banyak bertahun-tahun, dan saya merasa sedikit kotor menulis apa pun yang melibatkan kedua dokumen.writeln () dan eval ().]
sumber
This was a wrong move. Real code doesn't have an opening brace appearing in the middle of nowhere
- Saya tidak setuju (semacam): Saya sudah sering di blok digunakan masa lalu seperti ini untuk variabel lingkup di C . Kebiasaan ini diambil untuk beberapa waktu yang lalu ketika melakukan embedded C di mana variabel pada stack mengambil ruang, jadi jika mereka tidak lagi diperlukan, kami ingin ruang dibebaskan di akhir blok. Namun, ECMAScript hanya lingkup dalam blok fungsi () {}. Jadi, sementara saya tidak setuju bahwa konsepnya salah, saya setuju bahwa implementasi di JS ( mungkin ) salah.let
untuk mendeklarasikan variabel blok-dicakup.Saya solusi kedua @ Ventero. Jika Anda mau, Anda bisa masuk ke lebih detail tentang bagaimana
+
mengkonversi operan.Langkah pertama (§9.1): mengkonversi kedua operan ke primitif (nilai-nilai primitif yang
undefined
,null
, boolean, angka, string, semua nilai-nilai lain adalah objek, termasuk array dan fungsi). Jika operan sudah primitif, Anda selesai. Jika tidak, itu adalah objekobj
dan langkah-langkah berikut dilakukan:obj.valueOf()
. Jika mengembalikan primitif, Anda selesai. Contoh langsung dariObject
dan array kembali sendiri, sehingga Anda belum selesai.obj.toString()
. Jika mengembalikan primitif, Anda selesai.{}
dan[]
keduanya mengembalikan string, sehingga Anda selesai.TypeError
.Untuk tanggal, langkah 1 dan 2 ditukar. Anda dapat mengamati perilaku konversi sebagai berikut:
Interaksi (
Number()
mula-mula dikonversi ke primitif lalu ke nomor):Langkah kedua (§11.6.1): Jika salah satu operan adalah string, operan lainnya juga dikonversi menjadi string dan hasilnya dihasilkan dengan menggabungkan dua string. Jika tidak, kedua operan dikonversikan menjadi angka dan hasilnya dihasilkan dengan menambahkannya.
Penjelasan lebih rinci tentang proses konversi: “ Apa itu {} + {} dalam JavaScript? ”
sumber
Kami dapat merujuk pada spesifikasi dan itu hebat dan paling akurat, tetapi sebagian besar kasus juga dapat dijelaskan dengan cara yang lebih komprehensif dengan pernyataan berikut:
+
dan-
operator hanya bekerja dengan nilai primitif. Lebih khusus+
(penambahan) bekerja dengan string atau angka, dan+
(unary) dan-
(pengurangan dan unary) hanya bekerja dengan angka.valueOf
atautoString
, yang tersedia pada objek apa pun. Itulah alasan mengapa fungsi atau operator tidak melakukan kesalahan ketika dipanggil pada objek.Jadi kita dapat mengatakan bahwa:
[] + []
sama sepertiString([]) + String([])
yang sama dengan'' + ''
. Saya sebutkan di atas bahwa+
(penambahan) juga berlaku untuk angka, tetapi tidak ada representasi angka yang valid dari sebuah array dalam JavaScript, jadi penambahan string digunakan sebagai gantinya.[] + {}
sama sepertiString([]) + String({})
yang sama dengan'' + '[object Object]'
{} + []
. Yang ini layak mendapat penjelasan lebih lanjut (lihat jawaban Ventero). Dalam hal ini, kurung kurawal diperlakukan bukan sebagai objek tetapi sebagai blok kosong, sehingga ternyata sama dengan+[]
. Unary+
hanya bekerja dengan angka, sehingga implementasi mencoba untuk mendapatkan nomor dari[]
. Pertama ia mencobavalueOf
yang dalam hal array mengembalikan objek yang sama, maka ia mencoba upaya terakhir: konversitoString
hasil ke angka. Kita dapat menuliskannya+Number(String([]))
sama dengan+Number('')
yang sama+0
.Array(16).join("wat" - 1)
pengurangan-
hanya bekerja dengan angka, jadi sama seperti:Array(16).join(Number("wat") - 1)
, seperti"wat"
tidak dapat dikonversi ke angka yang benar. Kami menerimaNaN
, dan setiap operasi aritmatika padaNaN
hasil denganNaN
, jadi kita harus:Array(16).join(NaN)
.sumber
Untuk menopang apa yang telah dibagikan sebelumnya.
Penyebab yang mendasari perilaku ini sebagian karena sifat JavaScript yang diketik dengan lemah. Sebagai contoh, ekspresi 1 + “2” adalah ambigu karena ada dua kemungkinan interpretasi berdasarkan tipe operan (int, string) dan (int int):
Jadi dengan berbagai jenis input, kemungkinan output meningkat.
Algoritma penambahan
Primitif JavaScript adalah string, angka, null, tidak terdefinisi dan boolean (Simbol akan segera hadir di ES6). Nilai lain adalah objek (misalnya array, fungsi dan objek). Proses pemaksaan untuk mengubah objek menjadi nilai-nilai primitif dijelaskan sebagai berikut:
Jika nilai primitif dikembalikan ketika object.valueOf () dipanggil, maka kembalikan nilai ini, jika tidak lanjutkan
Jika nilai primitif dikembalikan ketika object.toString () dipanggil, maka kembalikan nilai ini, jika tidak lanjutkan
Lempar TypeError
Catatan: Untuk nilai tanggal, perintah ini untuk memanggil toString sebelum valueOf.
Jika nilai operan adalah string, maka lakukan penggabungan string
Jika tidak, konversikan kedua operan ke nilai numeriknya dan kemudian tambahkan nilai-nilai ini
Mengetahui berbagai nilai paksaan dari jenis-jenis dalam JavaScript memang membantu membuat hasil yang membingungkan menjadi lebih jelas. Lihat tabel paksaan di bawah ini
Juga baik untuk mengetahui bahwa operator + JavaScript adalah asosiatif kiri karena ini menentukan apa yang akan menjadi kasus yang melibatkan lebih dari satu operasi +.
Leveraging the Jadi 1 + "2" akan memberikan "12" karena penambahan apa pun yang melibatkan string akan selalu default ke rangkaian string.
Anda dapat membaca lebih banyak contoh di posting blog ini (disclaimer I wrote it).
sumber