Pengetahuan tingkat lanjut
Bagian ini membahas lebih dalam tentang internal string. Pengetahuan ini akan berguna bagi Anda jika Anda berencana berurusan dengan emoji, karakter matematika atau hieroglif langka, atau simbol langka lainnya.
Seperti yang telah kita ketahui, string JavaScript didasarkan pada Unicode: setiap karakter diwakili oleh urutan byte 1-4 byte.
JavaScript memungkinkan kita memasukkan karakter ke dalam string dengan menentukan kode Unicode heksadesimalnya dengan salah satu dari tiga notasi berikut:
xXX
XX
harus berupa dua digit heksadesimal dengan nilai antara 00
dan FF
, maka xXX
adalah karakter yang kode Unicodenya XX
.
Karena notasi xXX
hanya mendukung dua digit heksadesimal, notasi ini hanya dapat digunakan untuk 256 karakter Unicode pertama.
256 karakter pertama ini mencakup alfabet Latin, karakter sintaksis paling dasar, dan beberapa lainnya. Misalnya, "x7A"
sama dengan "z"
(Unicode U+007A
).
peringatan("x7A"); // z peringatan("xA9"); // ©, simbol hak cipta
uXXXX
XXXX
harus tepat 4 digit hex dengan nilai antara 0000
dan FFFF
, maka uXXXX
adalah karakter yang kode Unicode-nya XXXX
.
Karakter dengan nilai Unicode lebih besar dari U+FFFF
juga dapat direpresentasikan dengan notasi ini, namun dalam kasus ini, kita perlu menggunakan apa yang disebut pasangan pengganti (kita akan membahas tentang pasangan pengganti nanti di bab ini).
peringatan( "u00A9" ); // ©, sama seperti xA9, menggunakan notasi hex 4 digit peringatan( "u044F" ); // я, huruf alfabet Sirilik peringatan( "u2191" ); // ↑, simbol panah atas
u{X…XXXXXX}
X…XXXXXX
harus berupa nilai heksadesimal 1 hingga 6 byte antara 0
dan 10FFFF
(titik kode tertinggi yang ditentukan oleh Unicode). Notasi ini memungkinkan kita dengan mudah merepresentasikan semua karakter Unicode yang ada.
peringatan( "u{20331}" ); // 佫, karakter Cina yang langka (Unicode panjang) peringatan("u{1F60D}" ); // ?, simbol wajah tersenyum (Unicode panjang lainnya)
Semua karakter yang sering digunakan memiliki kode 2-byte (4 digit hex). Huruf dalam sebagian besar bahasa Eropa, angka, dan kumpulan ideografik CJK terpadu dasar (CJK – dari sistem penulisan Cina, Jepang, dan Korea), memiliki representasi 2 byte.
Awalnya, JavaScript didasarkan pada pengkodean UTF-16 yang hanya mengizinkan 2 byte per karakter. Namun 2 byte hanya mengizinkan 65536 kombinasi dan itu tidak cukup untuk setiap kemungkinan simbol Unicode.
Jadi simbol langka yang memerlukan lebih dari 2 byte dikodekan dengan sepasang karakter 2 byte yang disebut “pasangan pengganti”.
Sebagai efek sampingnya, panjang simbol tersebut adalah 2
:
peringatan('?'.panjang ); // 2, MODAL SKRIP MATEMATIKA X peringatan('?'.panjang ); // 2, WAJAH DENGAN AIR MATA KEBAHAGIAAN peringatan('?'.panjang ); // 2, karakter Cina yang langka
Itu karena pasangan pengganti tidak ada pada saat JavaScript dibuat, dan karenanya tidak diproses dengan benar oleh bahasa tersebut!
Kami sebenarnya memiliki satu simbol di setiap string di atas, tetapi properti length
menunjukkan panjang 2
.
Mendapatkan simbol juga bisa jadi rumit, karena sebagian besar fitur bahasa memperlakukan pasangan pengganti sebagai dua karakter.
Misalnya, di sini kita dapat melihat dua karakter ganjil pada keluaran:
peringatan('?'[0] ); // menampilkan simbol aneh... peringatan('?'[1] ); // ...bagian dari pasangan pengganti
Potongan-potongan pasangan pengganti tidak ada artinya tanpa satu sama lain. Jadi peringatan pada contoh di atas sebenarnya menampilkan sampah.
Secara teknis, pasangan pengganti juga dapat dideteksi berdasarkan kodenya: jika karakter memiliki kode dalam interval 0xd800..0xdbff
, maka itu adalah bagian pertama dari pasangan pengganti. Karakter berikutnya (bagian kedua) harus memiliki kode dalam interval 0xdc00..0xdfff
. Interval ini disediakan khusus untuk pasangan pengganti menurut standar.
Jadi metode String.fromCodePoint dan str.codePointAt ditambahkan dalam JavaScript untuk menangani pasangan pengganti.
Mereka pada dasarnya sama dengan String.fromCharCode dan str.charCodeAt, tetapi mereka memperlakukan pasangan pengganti dengan benar.
Perbedaannya dapat dilihat di sini:
// charCodeAt tidak mengetahui pasangan pengganti, jadi ia memberikan kode untuk bagian pertama ?: peringatan( '?'.charCodeAt(0).toString(16) ); // d835 // codePointAt mengetahui pasangan pengganti peringatan( '?'.codePointAt(0).toString(16) ); // 1d4b3, membaca kedua bagian dari pasangan pengganti
Artinya, jika kita mengambil dari posisi 1 (dan itu agak salah di sini), maka keduanya hanya mengembalikan bagian ke-2 dari pasangan tersebut:
peringatan( '?'.charCodeAt(1).toString(16) ); // dcb3 peringatan( '?'.codePointAt(1).toString(16) ); // dcb3 // paruh kedua pasangan yang tidak ada artinya
Anda akan menemukan lebih banyak cara untuk menangani pasangan pengganti nanti di bab Iterables. Mungkin ada perpustakaan khusus untuk itu juga, tapi tidak ada yang cukup terkenal untuk disarankan di sini.
Kesimpulan: memisahkan string pada titik sembarang itu berbahaya
Kita tidak bisa begitu saja membagi string pada posisi sembarang, misalnya mengambil str.slice(0, 4)
dan mengharapkannya menjadi string yang valid, misalnya:
peringatan('hai ?'.slice(0, 4) ); // Hai [?]
Di sini kita dapat melihat karakter sampah (paruh pertama dari pasangan pengganti senyuman) pada output.
Berhati-hatilah jika Anda ingin bekerja dengan andal dengan pasangan pengganti. Mungkin bukan masalah besar, tapi setidaknya Anda harus memahami apa yang terjadi.
Dalam banyak bahasa, terdapat simbol-simbol yang tersusun dari karakter dasar dengan tanda di atas/bawahnya.
Misalnya, huruf a
dapat menjadi karakter dasar untuk karakter berikut: àáâäãåā
.
Karakter “komposit” yang paling umum memiliki kodenya sendiri di tabel Unicode. Namun tidak semuanya, karena kemungkinan kombinasinya terlalu banyak.
Untuk mendukung komposisi arbitrer, standar Unicode memungkinkan kita menggunakan beberapa karakter Unicode: karakter dasar diikuti oleh satu atau banyak karakter “tanda” yang “menghiasnya”.
Misalnya, jika kita memiliki S
diikuti dengan karakter khusus “titik di atas” (kode u0307
), maka akan ditampilkan sebagai Ṡ.
peringatan('Su0307' ); // S
Jika kita memerlukan tanda tambahan di atas huruf (atau di bawahnya) – tidak masalah, cukup tambahkan karakter tanda yang diperlukan.
Misalnya, jika kita menambahkan karakter “titik di bawah” (kode u0323
), maka kita akan mendapatkan “S dengan titik di atas dan di bawah”: Ṩ
.
Misalnya:
peringatan('Su0307u0323' ); // S
Hal ini memberikan fleksibilitas yang besar, namun juga masalah yang menarik: dua karakter mungkin terlihat sama secara visual, namun diwakili dengan komposisi Unicode yang berbeda.
Misalnya:
misalkan s1 = 'Su0307u0323'; // Ṩ, S + titik di atas + titik di bawah misalkan s2 = 'Su0323u0307'; // Ṩ, S + titik di bawah + titik di atas peringatan( `s1: ${s1}, s2: ${s2}` ); waspada( s1 == s2 ); // salah meskipun karakternya terlihat sama (?!)
Untuk mengatasi hal ini, terdapat algoritma “Normalisasi Unicode” yang membawa setiap string ke bentuk “normal” tunggal.
Ini diimplementasikan oleh str.normalize().
alert( "Su0307u0323".normalisasi() == "Su0323u0307".normalisasi() ); // BENAR
Lucu sekali bahwa dalam situasi kita, normalize()
sebenarnya menyatukan urutan 3 karakter menjadi satu: u1e68
(S dengan dua titik).
alert( "Su0307u0323".normalisasi().panjang ); // 1 peringatan( "Su0307u0323".normalisasi() == "u1e68" ); // BENAR
Kenyataannya, hal ini tidak selalu terjadi. Alasannya adalah simbol Ṩ
“cukup umum”, sehingga pembuat Unicode memasukkannya ke dalam tabel utama dan memberinya kode.
Jika Anda ingin mempelajari lebih lanjut tentang aturan dan varian normalisasi – aturan dan varian tersebut dijelaskan dalam lampiran standar Unicode: Bentuk Normalisasi Unicode, namun untuk sebagian besar tujuan praktis, informasi dari bagian ini sudah cukup.