Keanehan pada Tipe Data Number dan toString di Javascript?

Tempo hari saya menemukan satu lagi kasus yang sekilas aneh, bahkan sekilas terlihat seperti bug di Javascript. Kejadian bermula ketika saya dihadapi pada keperluan meng-encode sebuah integer ke dalam string baru berbasis 36. Metode yang bisa digunakan adalah dengan fungsi Number.prototype.toString(radix) yang dimiliki oleh semua objek bertipe Number di Javascript.

Kemudian, mengingat sifat coercion Javascript, kita dapat memastikan bahwa tipe data primitif akan dibungkus sebagai objek sesuai tipe datanya ketika diperlukan. Saya pun berekspektasi bahwa dengan melakukan 22.toString(36) akan dibungkus menjadi (new Number(22)).toString(36), yang akan menghasilkan string m. Pada kenyataannya, apa yang terjadi?

22.toString(36);
> Uncaught SyntaxError: Invalid or unexpected token 

Hmmm. Aneh. Oke, kita coba bungkus angka tersebut dengan tanda kurung.

(22).toString(36);
> "m"

Ya, berjalan sesuai ekspektasi. Tapi tahukah Anda cara-cara apa lagi yang membuatnya berjalan sesuai ekspektasi? *smirk*

22.0.toString(36); // hmm ya oke bolehlah
> "m"

22..toString(36); // uuumm...
> "m"

22["toString"](36); // what the...
> "m"

22 .toString(); // woi!
> "m"

Aneh? Ya, sekilas. Bug? Belum tentu. Lalu kenapa bisa begitu?

Martabak: Mari Kita Bahas Kenapa

Ternyata yang menjadi permasalahan adalah notasi . dan interpretasi parser Javascript terhadapnya. Notasi . pada sebuah angka bisa bermakna pemisah bagian desimal atau akses properti objek Number via "auto-boxing". Dalam kasus 22.toString(36), parser Javascript selalu menduga bagian sebelah kanan notasi titik pada ekspresi seperti ekspresi tersebut adalah bagian desimal, sehingga terjadi error ketika ekspresi tersebut dievaluasi karena parser berekspektasi bagian kanan dari titik adalah angka, bukan huruf.

Bagaimana dengan kasus-kasus yang berhasil di atas? Mari kita telaah satu per satu.

22.0.toString(36);
> "m"

Ekspresi ini berjalan sesuai ekspektasi karena notasi titik (.) kedua tidak ambigu bagi parser, karena sudah ada notasi titik pertama yang menjadi pemisah bagian desimal. Notasi titik kedua pastilah menandakan perintah akses properti.

22..toString(36);
> "m"

Ini sebenarnya sama dengan sebelumnya, karena Javascript menganggap 22. sama dengan 22.0. Meskipun berbeda ekspresi, nilai keduanya sama.

22["toString"](36);
> "m"

Sekadar penyegaran, dalam Javascript kita dapat memanggil properti objek dengan bracket notation, ditulis seperti obj["key"], yang ekuivalen dengan obj.key. Pada kasus ini, parser melihat bracket notation ["toString"] di belakang 22, sehingga tidak ada keraguan akan intensi ekspresi tersebut, yaitu memanggil properti toString dari 22, yang di-auto-boxing menjadi (new Number(22)). Properti tersebut mengembalikan sebuah fungsi, sehingga (36) di akhir menjadi parameter eksekusi fungsi tersebut. Tidak cantik, tapi berjalan.

22 .toString(36);
> "m"

Ini adalah metode yang paling "hacky" dan tidak direkomendasikan karena sangat tidak readable dan mudah disalahartikan oleh manusia yang membacanya. Perhatikan adanya spasi antara 22 dan ., itu yang menyebabkan ekspresi ini bisa berjalan tanpa error.

Dua poin penting di sini adalah:

  1. Javascript tidak memedulikan whitespace.
  2. Javascript tidak memperbolehkan whitespace sebelum bagian desimal suatu Number.

Karena dua aturan itu, whitespace sebelum notasi . tidak dianggap dan tidak menimbulkan syntax error, dan notasi . tersebut tidak dianggap ambigu karena parser dapat mencoret kemungkinan notasi . tersebut menandakan pemisah bagian desimal.


Fenomena ini secara umum dapat kita bilang disebabkan oleh dua karakter Javascript. Weak typing menyebabkan tidak adanya pembeda antara tipe data integer dan float, sehingga parser harus menerka makna notasi . yang mengikuti sebuah angka. Primitive type coercion menyebabkan value dari tipe data primitif seketika dikonversi menjadi objek sesuai tipe datanya ketika diperlukan, contohnya ketika mengakses properti-properti pada objek induknya langsung dari tipe data primitifnya.

Ternyata, ketika kedua karakteristik Javascript tersebut bertemu, bisa terjadi ambiguitas seperti pada kasus ini. Ini bisa menjadi catatan bagi para developer Javascript untuk terus mendalami dan memperhatikan mekanisme inti Javascript dalam menulis kode Javascript.

Sekian saja yang bisa saya bagikan saat ini, semoga bermanfaat!

Sumber-sumber:

  1. A Drip of Javascript: Numbers and JavaScript's Dot Notation
  2. Stack Overflow: Is it possible to base 36 encode with JavaScript / jQuery?