Kenapa ini penting
Di modul representasi teks kamu sudah tahu bahwa setiap karakter punya nomor. Sekarang kita bahas standarnya secara nyata: ASCII, Unicode, dan UTF-8. Ini penting karena aplikasi modern dipakai orang dari seluruh dunia. Nama pengguna bisa berisi huruf Jepang, komentar bisa penuh emoji, dan alamat bisa memakai huruf beraksen. JavaScript punya beberapa sifat unik soal teks, misalnya "๐".length bernilai 2, bukan 1. Kalau tidak paham alasannya, kamu bisa membuat fitur "batas 280 karakter" yang memotong emoji jadi setengah dan menampilkan karakter rusak ๏ฟฝ.
Konsep inti
ASCII: standar awal 7 bit
ASCII (American Standard Code for Information Interchange, 1963) memetakan 128 karakter (0-127) memakai 7 bit:
| Rentang | Isi |
|---|---|
| 0-31 | Karakter kontrol, misalnya 9 = tab, 10 = newline, 13 = carriage return |
| 32 | Spasi |
| 48-57 | Angka 0-9 |
| 65-90 | Huruf besar A-Z |
| 97-122 | Huruf kecil a-z |
Pola menarik: huruf kecil = huruf besar + 32. A (65) dan a (97) selisihnya 32. Angka "0" bernomor 48, jadi karakter "7" bernomor 55, bukan 7.
Masalahnya, ASCII hanya untuk bahasa Inggris. Tidak ada รฉ, รฑ, huruf Arab, Mandarin, apalagi emoji. Dulu tiap negara membuat tabel sendiri yang saling bentrok, dan itulah sumber mojibake.
Unicode: satu nomor untuk setiap karakter di dunia
Unicode adalah katalog raksasa yang memberi setiap karakter sebuah nomor unik yang disebut code point, ditulis U+ diikuti heksadesimal:
| Karakter | Code point | Desimal |
|---|---|---|
A |
U+0041 | 65 |
รฉ |
U+00E9 | 233 |
โฌ |
U+20AC | 8364 |
๊ฆฒ (aksara Jawa "ha") |
U+A9B2 | 43442 |
๐ |
U+1F600 | 128512 |
128 code point pertama Unicode sama persis dengan ASCII, jadi teks ASCII lama tetap kompatibel. Unicode menyediakan ruang sampai U+10FFFF (lebih dari satu juta posisi).
UTF-8: cara hemat menyimpan Unicode
Unicode hanya memberi nomor. Untuk menyimpannya ke byte, dipakai encoding. Yang paling populer adalah UTF-8, dipakai oleh sekitar 98% website. UTF-8 memakai 1 sampai 4 byte tergantung besar nomornya:
| Rentang code point | Jumlah byte | Contoh |
|---|---|---|
| U+0000 - U+007F | 1 | A |
| U+0080 - U+07FF | 2 | รฉ |
| U+0800 - U+FFFF | 3 | โฌ, ๊ฆฒ |
| U+10000 - U+10FFFF | 4 | ๐ |
Kelebihannya: teks bahasa Inggris tetap 1 byte per huruf (hemat), tapi semua karakter dunia tetap bisa ditulis.
Bagaimana JavaScript menyimpan string: UTF-16
Di dalam memori, string JavaScript disimpan sebagai deretan UTF-16 code unit (masing-masing 16 bit). Karakter dengan code point di atas U+FFFF, seperti kebanyakan emoji, tidak muat dalam satu unit. Karakter itu dipecah menjadi dua code unit yang disebut surrogate pair. Akibatnya:
.lengthmenghitung code unit, bukan karakter yang kamu lihat."๐".lengthโ2.charCodeAt(i)mengembalikan code unit (0-65535). Untuk emoji, hasilnya hanya "separuh" emoji.codePointAt(i)mengembalikan code point utuh.String.fromCharCode(...)membuat string dari code unit.String.fromCodePoint(...)membuat string dari code point (aman untuk emoji).for...ofdan spread[...teks]mengiterasi per code point, jadi emoji tunggal tidak terbelah.
Masih ada satu lapis lagi: beberapa emoji adalah gabungan beberapa code point. Contohnya emoji keluarga ๐จโ๐ฉโ๐ง adalah 3 orang yang disambung karakter tak terlihat ZWJ (zero width joiner). [...teks] menghitungnya 5. Untuk menghitung "karakter yang dilihat manusia" (grapheme), JavaScript modern menyediakan Intl.Segmenter.
Huruf yang terlihat sama tapi berbeda
รฉ bisa ditulis sebagai satu code point (U+00E9) atau sebagai e + tanda aksen gabungan (U+0301). Tampilannya identik, tapi === bernilai false. Method normalize() menyamakan bentuknya sebelum dibandingkan.
Contoh kode
// ASCII dasar
console.log("A".charCodeAt(0)); // โ 65
console.log("a".charCodeAt(0)); // โ 97
console.log("7".charCodeAt(0) - 48); // โ 7 (ubah karakter digit ke angka)
console.log(String.fromCharCode(72, 105)); // โ Hi
// Mengubah huruf kecil ke besar secara manual (selisih 32)
console.log(String.fromCharCode("k".charCodeAt(0) - 32)); // โ K
// Emoji: length, charCodeAt vs codePointAt
const senyum = "๐";
console.log(senyum.length); // โ 2 (dua code unit UTF-16)
console.log(senyum.charCodeAt(0)); // โ 55357 (separuh surrogate pair)
console.log(senyum.codePointAt(0)); // โ 128512
console.log(senyum.codePointAt(0).toString(16)); // โ 1f600 (U+1F600)
console.log(String.fromCodePoint(128512)); // โ ๐
console.log([...senyum].length); // โ 1 (spread memecah per code point)
// Jumlah byte di UTF-8
const enc = new TextEncoder();
for (const ch of ["A", "รฉ", "โฌ", "๐"]) {
console.log(ch, enc.encode(ch).length, "byte");
}
// โ A 1 byte
// โ รฉ 2 byte
// โ โฌ 3 byte
// โ ๐ 4 byte
Memotong teks dengan aman dan menghitung karakter yang terlihat:
const status = "Halo ๐๐";
console.log(status.length); // โ 9
console.log(status.slice(0, 6)); // โ "Halo " + separuh emoji (tampil rusak ๏ฟฝ)
console.log([...status].slice(0, 6).join("")); // โ Halo ๐ (aman)
// Emoji gabungan: hitung grapheme dengan Intl.Segmenter
const keluarga = "๐จโ๐ฉโ๐ง";
console.log(keluarga.length); // โ 8
console.log([...keluarga].length); // โ 5
const seg = new Intl.Segmenter("id", { granularity: "grapheme" });
console.log([...seg.segment(keluarga)].length); // โ 1
// Normalisasi huruf beraksen
const a = "รฉ"; // satu code point U+00E9
const b = "eฬ"; // e + aksen gabungan
console.log(a === b); // โ false
console.log(a.normalize("NFC") === b.normalize("NFC")); // โ true
Kesalahan umum
- Memakai
.lengthuntuk batas karakter yang dilihat pengguna. Emoji terhitung 2 atau lebih. Gunakan[...teks].lengthatauIntl.Segmenteruntuk hitungan yang lebih akurat. - Memakai
charCodeAtataufromCharCodeuntuk emoji. GunakancodePointAtdanString.fromCodePoint. - Memotong string dengan
slicedi tengah emoji. Hasilnya karakter rusak๏ฟฝ. Pecah dulu dengan[...teks]sebelum dipotong. - Mengira karakter
"5"sama dengan angka 5. Kode karakter"5"adalah 53. GunakanNumber("5")untuk mendapatkan angkanya. - Membandingkan teks beraksen tanpa
normalize(). Dua teks yang terlihat sama bisa berbeda code point.
Latihan
- Mudah, Tampilkan code point (dalam desimal dan format
U+XXXX) dari karakter"Z","รฑ", dan"โฌ". - Sedang, Buat function
hitungKarakter(teks)yang menghitung jumlah code point (bukan code unit). Uji dengan"Aku ๐ JS". - Menantang, Buat function
potongAman(teks, maks)yang memotong teks menjadi maksimalmaksgrapheme dan menambahkan"โฆ"kalau terpotong. Uji dengan"Mantap ๐จโ๐ฉโ๐ง sekali"danmaks = 8.
Lihat solusi
1.
for (const ch of ["Z", "รฑ", "โฌ"]) {
const cp = ch.codePointAt(0);
const uplus = "U+" + cp.toString(16).toUpperCase().padStart(4, "0");
console.log(ch, cp, uplus);
}
// โ Z 90 U+005A
// โ รฑ 241 U+00F1
// โ โฌ 8364 U+20AC
2.
function hitungKarakter(teks) {
return [...teks].length;
}
console.log("Aku ๐ JS".length); // โ 9
console.log(hitungKarakter("Aku ๐ JS")); // โ 8
3.
function potongAman(teks, maks) {
const seg = new Intl.Segmenter("id", { granularity: "grapheme" });
const potongan = [...seg.segment(teks)].map((s) => s.segment);
if (potongan.length <= maks) return teks;
return potongan.slice(0, maks).join("") + "โฆ";
}
console.log(potongAman("Mantap ๐จโ๐ฉโ๐ง sekali", 8)); // โ Mantap ๐จโ๐ฉโ๐งโฆ
Ringkasan
- ASCII memetakan 128 karakter dasar (A = 65, a = 97, "0" = 48).
- Unicode memberi setiap karakter dunia sebuah code point (
U+1F600untuk ๐). 128 pertamanya sama dengan ASCII. - UTF-8 menyimpan code point dalam 1-4 byte dan merupakan standar web.
- String JavaScript memakai UTF-16, sehingga emoji bisa terhitung 2 di
.length. - Gunakan
codePointAt,String.fromCodePoint,[...teks], danIntl.Segmenteruntuk teks yang mengandung emoji.