Pesan "{Anda Tidak Tahu JS} Jenis dan Konstruksi Grammar"

gambar Apa pun pengalaman pemrograman JavaScript yang Anda miliki, kemungkinan besar Anda tidak sepenuhnya memahami bahasa tersebut. Panduan ringkas ini mengeksplorasi jenis-jenis lebih dalam dari semua buku yang ada: Anda akan mempelajari cara kerja jenis, tentang masalah konversi mereka, dan mempelajari cara menggunakan fitur baru.

Seperti buku-buku lain dalam seri “You Don't Know JS” , ini menunjukkan aspek-aspek non-sepele dari bahasa yang lebih disukai oleh para programmer JavaScript untuk menjauh dari (atau menganggap bahwa mereka tidak ada). Berbekal pengetahuan ini, Anda akan mencapai penguasaan JavaScript yang sebenarnya.

Kutipan. Kesetaraan itu ketat dan tidak ketat.


Kesetaraan non-ketat diperiksa oleh operator ==, dan kesetaraan ketat oleh operator ===. Kedua operator digunakan untuk membandingkan dua nilai untuk "kesetaraan", tetapi pilihan bentuk (ketat / tidak ketat) mengarah pada perbedaan perilaku yang sangat penting, terutama dalam bagaimana keputusan dibuat atas kesetaraan.

Ada kesalahpahaman umum mengenai dua operator ini: "== memeriksa persamaan nilai, dan === memeriksa persamaan nilai dan tipe." Kedengarannya masuk akal
tapi tidak akurat. Banyak buku dan blog JavaScript yang memiliki reputasi baik mengatakan hal itu, tetapi sayangnya semuanya salah.

Deskripsi yang benar adalah: "== memungkinkan konversi tipe saat memeriksa kesetaraan, dan === melarang konversi tipe."

Kinerja Verifikasi Kesetaraan


Berhentilah dan pikirkan perbedaan penjelasan pertama (tidak akurat) dari penjelasan kedua (tepat).
Dalam penjelasan pertama, tampak jelas bahwa operator === melakukan lebih banyak pekerjaan daripada == karena ia juga perlu memeriksa jenisnya.

Dalam penjelasan kedua, operator == melakukan lebih banyak pekerjaan, karena dengan tipe yang berbeda ia harus melalui konversi tipe.

Jangan jatuh ke dalam perangkap yang banyak jatuh ke dalam. Jangan berpikir bahwa ini entah bagaimana akan mempengaruhi kecepatan program, dan == akan lebih lambat ===. Meskipun konversi membutuhkan waktu, ini membutuhkan hitungan mikrodetik (ya, sepersejuta detik).

Jika Anda membandingkan dua nilai dari tipe yang sama, == dan === menggunakan algoritma yang sama, jadi jika Anda tidak memperhitungkan perbedaan kecil dalam implementasi mesin, mereka harus melakukan
dan pekerjaan yang sama.

Jika Anda membandingkan dua nilai dari tipe yang berbeda, kinerja bukanlah faktor penting. Anda harus bertanya pada diri sendiri sesuatu yang lain: jika saya membandingkan dua nilai, apakah saya ingin jenis konversi terjadi atau tidak?

Jika Anda memerlukan konversi, gunakan persamaan == non-ketat, dan jika konversi tidak diinginkan, gunakan persamaan = = ketat.

Kedua operator, == dan ===, memeriksa jenis operan mereka. Perbedaannya adalah bagaimana mereka merespons tipe ketidakcocokan.

Pemeriksaan kesetaraan abstrak


Perilaku operator == didefinisikan dalam bagian 11.9.3 dari spesifikasi ES5 (“Algoritma Pemeriksa Kesetaraan Abstrak”). Berikut ini adalah algoritme yang terperinci tetapi sederhana, dengan daftar eksplisit dari semua kemungkinan kombinasi jenis dan metode konversi tipe (jika perlu) yang harus diterapkan dalam setiap kombinasi.

Ketika seseorang mengutuk konversi tipe (implisit) sebagai terlalu kompleks dan mengandung terlalu banyak cacat untuk penggunaan praktis yang bermanfaat, ia mengutuk aturan "cek kesetaraan abstrak". Biasanya dikatakan bahwa mekanisme ini terlalu rumit dan tidak wajar untuk dipelajari dan digunakan secara praktis, dan itu membuat kesalahan dalam program JS daripada menyederhanakan pembacaan kode.

Saya percaya bahwa ini adalah asumsi yang salah - Anda pembaca adalah pengembang yang kompeten yang menulis algoritma, yaitu kode (dan juga membaca dan memahaminya), sepanjang hari. Untuk alasan ini, saya akan mencoba menjelaskan "tes kesetaraan abstrak" dengan kata-kata sederhana. Namun, saya juga merekomendasikan membaca bagian 11.9.3 dari spesifikasi ES5. Saya pikir itu akan mengejutkan Anda betapa logis semuanya ada di sana.

Bahkan, bagian pertama (11.9.3.1) menyatakan bahwa jika dua nilai yang dibandingkan memiliki tipe yang sama, mereka dibandingkan dengan cara yang sederhana dan alami. Sebagai contoh, 42 hanya 42, dan string "abc" hanya "abc".

Beberapa pengecualian kecil yang perlu diingat:

  • Nilai NaN tidak pernah sama dengan dirinya sendiri (lihat bab 2).
  • +0 dan -0 sama satu sama lain (lihat bab 2).

Bagian terakhir di bagian 11.9.3.1 dikhususkan untuk uji ketat == kesetaraan dengan objek (termasuk fungsi dan array). Dua nilai tersebut sama hanya jika keduanya merujuk ke nilai yang sama persis. Tidak ada konversi jenis yang dilakukan.

Pemeriksaan kesetaraan yang ketat === didefinisikan secara identik ke 11.9.3.1, termasuk ketentuan untuk dua nilai objek. Fakta ini sangat sedikit diketahui, tetapi == dan === berperilaku sama persis ketika membandingkan dua objek!

Sisa dari algoritme dalam 11.9.3 menunjukkan bahwa kesetaraan tidak-ketat == dapat digunakan untuk membandingkan dua jenis nilai yang berbeda, satu atau keduanya akan membutuhkan
konversi implisit. Sebagai hasil dari konversi, penunjukan dikonversi ke satu jenis, setelah itu mereka dapat langsung dibandingkan untuk kesetaraan dengan identitas sederhana
nilai-nilai.

Operasi pemeriksaan ketimpangan yang lemah! = Ditentukan persis seperti yang diharapkan; pada kenyataannya, operasi == sepenuhnya dilaksanakan, diikuti oleh perhitungan
penolakan hasil. Hal yang sama berlaku untuk operasi pengecekan ketimpangan secara ketat! ==.

Perbandingan: string dan angka


Untuk menunjukkan konversi ==, pertama buat contoh string dan angka, yang dilakukan sebelumnya dalam bab ini:

var a = 42; var b = "42"; a === b; // false a == b; // true 

Seperti yang diharapkan, centang a === b gagal karena konversi tidak diizinkan, dan nilai 42 dan "42" berbeda.

Namun, dalam perbandingan kedua a == b, kesetaraan tidak ketat digunakan; ini berarti bahwa jika jenisnya berbeda, algoritma perbandingan akan melakukan konversi implisit dari salah satu
atau keduanya.

Tetapi konversi seperti apa yang dilakukan di sini? Akankah nilai a, yaitu, 42, menjadi string, atau akankah nilai b "42" menjadi angka? Spesifikasi ES5 dalam bagian 11.9.3.4–5 mengatakan:

  1. Jika Tipe (x) bertipe Number, dan Tipe (y) bertipe String, kembalikan hasil perbandingan x == ToNumber (y).
  2. Jika Tipe (x) bertipe String dan Tipe (y) bertipe Number, kembalikan hasil perbandingan ToNumber (x) == y.

Dalam spesifikasi, nama formal dari tipe Number dan String digunakan, sedangkan dalam buku untuk tipe primitif, notasi number dan string biasanya digunakan. Jangan bingung kasus simbol Angka dalam spesifikasi dengan fungsi Nomor () bawaan. Untuk keperluan kita, kasus karakter atas nama jenis tidak memainkan peran - mereka memiliki arti yang sama.

Spesifikasi mengatakan bahwa nilai "42" dikonversi ke angka untuk perbandingan. Tentang bagaimana konversi dilakukan, sudah dijelaskan sebelumnya, dan secara khusus ketika menggambarkan ToNumber operasi abstrak. Dalam hal ini, cukup jelas
bahwa dua nilai yang dihasilkan dari 42 adalah sama.

Perbandingan: apa pun dengan boolean


Salah satu perangkap paling berbahaya dalam konversi implisit dari tipe == ditemui ketika mencoba untuk langsung membandingkan nilai dengan true atau false.

Contoh:

 var a = "42"; var b = true; a == b; // false 

Tunggu, apa yang terjadi di sini? Kita tahu bahwa "42" adalah arti sebenarnya (lihat sebelumnya dalam bab ini). Bagaimana hasilnya dibandingkan dengan benar dengan pernyataan kesetaraan ==
tidak memberikan yang benar?

Alasannya sederhana dan licik secara licik. Sangat mudah untuk salah paham, banyak pengembang JS tidak berusaha untuk memahaminya sepenuhnya.

Sekali lagi kami mengutip spesifikasinya, bagian 11.9.3.6–7:

  1. Jika Tipe (x) adalah tipe Boolean, kembalikan hasil perbandingan ToNumber (x) == y.
  2. Jika Tipe (y) adalah tipe Boolean, kembalikan hasil perbandingan x == ToNumber (y).

Mari kita lihat apa yang ada di sini. Langkah pertama:

 var x = true; var y = "42"; x == y; // false 

Jenis (x) benar-benar milik tipe Boolean, sehingga operasi ToNumber (x) dilakukan, yang mengkonversi true menjadi 1. Sekarang kondisi 1 == "42" dihitung. Jenisnya masih berbeda, oleh karena itu (hampir secara rekursif) algoritme berulang; seperti pada kasus sebelumnya, "42" dikonversi menjadi 42, dan kondisi 1 == 42 jelas salah.

Jika Anda menukar operan, hasilnya akan tetap sama:

 var x = "42"; var y = false; x == y; // false 

Kali ini, Tipe (y) adalah tipe Boolean, jadi ToNumber (y) memberikan 0. Kondisi "42" == 0 secara rekursif berubah menjadi 42 == 0, yang, tentu saja, salah.

Dengan kata lain, nilai "42" bukanlah == benar atau == salah. Pada pandangan pertama, pernyataan ini tampaknya sama sekali tidak terpikirkan. Bagaimana makna tidak benar atau salah?

Tapi ini masalahnya! Anda mengajukan pertanyaan yang salah. Meskipun sebenarnya itu bukan kesalahan Anda, itu adalah otak yang menipu Anda.

Nilai "42" memang benar, tetapi konstruksi "42" == true tidak melakukan tes boolean / transform sama sekali, apa pun yang dikatakan otak Anda. "42" tidak dikonversi ke boolean (true); sebaliknya, true dikonversi ke 1, dan kemudian "42" dikonversi menjadi 42.

Suka atau tidak, ToBoolean sama sekali tidak digunakan di sini, jadi kebenaran atau kepalsuan "42" sama sekali tidak penting untuk operasi ==! Penting untuk memahami bagaimana algoritma perbandingan == berperilaku di semua kombinasi jenis yang berbeda. Jika nilai boolean di satu sisi, maka selalu dikonversi ke angka terlebih dahulu.

Jika ini terasa aneh bagi Anda, Anda tidak sendirian. Secara pribadi, saya sarankan tidak pernah, dalam keadaan apa pun, untuk menggunakan == true atau == false. Tidak pernah.

Tetapi ingat bahwa saya hanya berbicara tentang == di sini. Konstruksi === true dan === false tidak mengizinkan konversi tipe, sehingga mereka terlindung dari konversi ToNumber yang disembunyikan.

Contoh:

 var a = "42"; //  (  !): if (a == true) { // .. } //   (  !): if (a === true) { // .. } //   ( ): if (a) { // .. } //  ( ): if (!!a) { // .. } //   ( ): if (Boolean( a )) { // .. } 

Jika Anda menghindari == benar atau == salah (longgar persamaan dengan boolean) dalam kode Anda, Anda tidak perlu khawatir tentang perangkap kebenaran / kepalsuan ini.

Perbandingan: null dengan undefined


Contoh lain dari konversi implisit terjadi ketika Anda menggunakan lax == kesetaraan antara nilai null dan undefined. Sekali lagi, saya akan mengutip spesifikasi ES5,
bagian 11.9.3.2–3:

  1. Jika x berisi nol dan y berisi tidak terdefinisi, kembalikan true.
  2. Jika x berisi tidak terdefinisi dan y berisi nol, kembalikan benar.

Tidak ada dan tidak terdefinisi jika dibandingkan dengan operator yang tidak ketat == sama satu sama lain (yaitu, mereka dikonversi satu sama lain), dan tidak ada nilai lain dalam keseluruhan bahasa.

Bagi kami, ini berarti bahwa nol dan tidak terdefinisi dapat dianggap tidak dapat dibedakan untuk tujuan perbandingan, jika Anda menggunakan operator pengujian kesetaraan yang tidak ketat ==, yang memungkinkan konversi implisit satu sama lain:

 var a = null; var b; a == b; // true a == null; // true b == null; // true a == false; // false b == false; // false a == ""; // false b == ""; // false a == 0; // false b == 0; // false 

Konversi antara nol dan tidak terdefinisi aman dan dapat diprediksi, dan tidak ada nilai lain yang dapat memberikan positif palsu untuk cek semacam itu. Saya sarankan menggunakan konversi ini sehingga nol dan tidak terdefinisi tidak berbeda dalam program dan ditafsirkan sebagai nilai tunggal.

Contoh:

 var a = doSomething(); if (a == null) { // .. } 

Pemeriksaan a == null hanya lulus jika doSomething () mengembalikan nol atau tidak terdefinisi dan gagal untuk nilai lainnya (termasuk 0, false, dan "").

Bentuk eksplisit cek ini, yang melarang konversi jenis apa pun, terlihat (menurut saya) jauh lebih jelek dan mungkin bekerja sedikit kurang efisien!

 var a = doSomething(); if (a === undefined || a === null) { // .. } 

Saya percaya bahwa bentuk == null adalah contoh lain dari situasi di mana konversi implisit membuatnya lebih mudah untuk membaca kode, tetapi apakah itu andal dan aman.

Perbandingan: objek dan non-objek


Jika objek / fungsi / array dibandingkan dengan skalar primitif sederhana (string, angka atau boolean), spesifikasi ES5 mengatakan yang berikut (bagian 11.9.3.8–9):

  1. Jika Tipe (x) adalah tipe String atau Angka, dan Tipe (y) adalah tipe Objek, kembalikan hasil perbandingan x == ToPrimitive (y).
  2. Jika Tipe (x) adalah tipe Objek dan Tipe (y) adalah tipe String atau Angka, kembalikan hasil perbandingan ToPrimitive (x) == y.

Anda mungkin memperhatikan bahwa di bagian spesifikasi ini hanya String dan Number yang disebutkan, tetapi tidak Boolean. Faktanya adalah bahwa, sebagaimana disebutkan di atas, bagian 11.9.3.6–7 memastikan bahwa setiap operan Boolean pertama kali direpresentasikan sebagai Number.

Contoh:

 var a = 42; var b = [ 42 ]; a == b; // true 

Untuk nilai [42], operasi abstrak ToPrimitive disebut (lihat "Operasi abstrak"), yang memberikan hasil "42". Sejak saat ini, kondisi sederhana "42" == 42 tetap, yang, seperti yang telah kami ketahui, berubah menjadi 42 == 42, sehingga a dan b sama hingga mengetik konversi.

Seperti yang Anda harapkan, semua fitur operasi ToPrimitive abstrak yang dibahas sebelumnya dalam bab ini ((toString (), valueOf ()) juga berlaku dalam kasus ini. Ini bisa sangat berguna jika Anda memiliki struktur data yang kompleks dan ingin mendefinisikan metode khusus valueOf () untuknya, yang harus memberikan nilai sederhana untuk keperluan pemeriksaan kesetaraan.

Bab 3 memeriksa "membongkar" pembungkus objek di sekitar nilai primitif (seperti dalam String baru ("abc"), misalnya), menghasilkan kembalinya primitif yang mendasarinya
nilai ("abc"). Perilaku ini terkait dengan transformasi ToPrimitive dalam algoritma ==:

 var a = "abc"; var b = Object( a ); //  ,  `new String( a )` a === b; // false a == b; // true 

a == b benar karena b dikonversi (atau "dibongkar") oleh operasi ToPrimitive ke basis sederhana nilai skalar primitif "abc", yang cocok dengan nilai dari a.

Ada beberapa nilai yang tidak demikian karena aturan utama lainnya dalam algoritma ==. Contoh:

 var a = null; var b = Object( a ); //  ,  `Object()` a == b; // false var c = undefined; var d = Object( c ); //  ,  `Object()` c == d; // false var e = NaN; var f = Object( e ); //  ,  `new Number( e )` e == f; // false 

Nilai-nilai nol dan tidak terdefinisi tidak dapat dikemas (mereka tidak memiliki pembungkus objek yang setara), jadi Object (null) tidak berbeda secara mendasar dari Object (): kedua panggilan membuat yang biasa
objek ny.

NaN dapat dipaket dalam Number objek wrapper yang sama, tetapi ketika == menyebabkan pembongkaran, perbandingan NaN == NaN gagal, karena nilai NaN tidak pernah sama dengan dirinya sendiri (lihat bab 2).

»Informasi lebih lanjut tentang buku ini dapat ditemukan di situs web penerbit
» Isi
» Kutipan

Kupon diskon 25% untuk penjaja - JavaScript

Setelah pembayaran versi kertas buku, sebuah buku elektronik dikirim melalui email.

Source: https://habr.com/ru/post/id456730/


All Articles