Kenapa ini penting
Sampai sekarang, semua kunci object yang kamu tulis berupa string. Masalahnya, string itu bisa "tabrakan". Bayangkan kamu memakai library pihak ketiga yang menambahkan properti id ke object pengguna, padahal kodemu sendiri juga memakai id untuk hal lain. Salah satu akan menimpa yang lain, dan bug seperti ini sulit dicari.
JavaScript menyediakan tipe data primitif khusus bernama Symbol untuk mengatasi ini. Symbol adalah pengenal yang dijamin unik. Selain untuk mencegah tabrakan nama, Symbol juga dipakai JavaScript sendiri sebagai "kait" (hook) untuk mengatur perilaku bawaan object, misalnya supaya object buatanmu bisa di-loop dengan for...of atau di-spread seperti array. Kamu mungkin tidak menulis Symbol setiap hari, tapi kamu pasti akan membaca kode library yang memakainya, dan Symbol adalah kunci untuk memahami modul iterator nanti.
Konsep inti
Membuat Symbol
Symbol("deskripsi") membuat Symbol baru. Teks deskripsi hanya label untuk membantu debugging, bukan identitasnya. Dua Symbol dengan deskripsi sama tetap berbeda:
console.log(Symbol("id") === Symbol("id")); // → false
Analogi: Symbol seperti nomor seri unik yang dicetak di setiap ponsel. Dua ponsel bisa sama-sama bertuliskan "Ponsel Biru" di kotaknya (deskripsi), tapi nomor serinya pasti beda. Perhatikan juga bahwa Symbol dibuat tanpa new. Menulis new Symbol() akan melempar TypeError.
Symbol sebagai kunci object
Symbol bisa dipakai sebagai kunci dengan computed property: { [kunciRahasia]: nilai }. Kunci Symbol punya sifat khusus:
- Tidak muncul di
for...in,Object.keys,Object.values,Object.entries. - Diabaikan oleh
JSON.stringify. - Tetap bisa dibaca kalau kamu punya Symbol-nya, dan bisa ditemukan lewat
Object.getOwnPropertySymbols(obj)atauReflect.ownKeys(obj).
Jadi Symbol bukan fitur keamanan atau privasi sungguhan. Ia lebih tepat disebut "tersembunyi dari iterasi biasa" dan "bebas tabrakan nama".
Symbol.for: registry global
Kadang kamu justru ingin Symbol yang sama dipakai di beberapa tempat berbeda, misalnya di file atau modul lain. Symbol.for("kunci") mencari Symbol dengan kunci itu di registry global; kalau belum ada, ia membuatnya. Panggilan berikutnya dengan kunci yang sama mengembalikan Symbol yang sama persis. Symbol.keyFor(sym) melakukan kebalikannya: mengembalikan kunci teks dari Symbol yang terdaftar.
| Pembuatan | Unik setiap panggilan? | Bisa dicari ulang? |
|---|---|---|
Symbol("x") |
ya | tidak |
Symbol.for("x") |
tidak, dibagi global | ya, lewat Symbol.for("x") |
Well-known symbols
JavaScript punya sejumlah Symbol bawaan yang disebut well-known symbols. Kalau object kamu punya method dengan kunci Symbol ini, JavaScript akan memakainya untuk operasi tertentu:
| Symbol | Dipakai saat |
|---|---|
Symbol.iterator |
for...of, spread [...obj], Array.from |
Symbol.toPrimitive |
object diubah ke number/string (misal +obj, `${obj}`) |
Symbol.toStringTag |
Object.prototype.toString.call(obj) |
Symbol.asyncIterator |
for await...of |
Yang paling penting adalah Symbol.iterator. Array, string, Map, dan Set bisa di-loop dengan for...of justru karena mereka punya method [Symbol.iterator]. Object biasa tidak punya, makanya for (const x of {}) menghasilkan error. Kita akan membahas protokol iterator secara lengkap di modul 13; di sini kamu cukup melihat contoh singkatnya.
Mengubah Symbol menjadi teks
Symbol tidak bisa otomatis digabung dengan string ("a" + sym melempar TypeError). Kamu harus eksplisit: String(sym) atau sym.toString() menghasilkan "Symbol(deskripsi)", dan sym.description menghasilkan deskripsinya saja.
Contoh kode
"use strict";
// 1) Setiap Symbol unik
const id1 = Symbol("id");
const id2 = Symbol("id");
console.log(id1 === id2); // → false
console.log(id1.description); // → id
console.log(String(id1)); // → Symbol(id)
console.log(typeof id1); // → symbol
// 2) Symbol sebagai kunci: bebas tabrakan dengan kunci string
const ID_INTERNAL = Symbol("idInternal");
const user = { id: "U-001", nama: "Maya", [ID_INTERNAL]: 98765 };
console.log(user.id, user[ID_INTERNAL]); // → U-001 98765
console.log(Object.keys(user)); // → [ 'id', 'nama' ]
console.log(JSON.stringify(user)); // → {"id":"U-001","nama":"Maya"}
console.log(Object.getOwnPropertySymbols(user)); // → [ Symbol(idInternal) ]
console.log(Reflect.ownKeys(user)); // → [ 'id', 'nama', Symbol(idInternal) ]
// 3) Symbol.for: Symbol bersama lewat registry global
const a = Symbol.for("app.versi");
const b = Symbol.for("app.versi");
console.log(a === b); // → true
console.log(Symbol.keyFor(a)); // → app.versi
console.log(Symbol.keyFor(id1)); // → undefined (bukan dari registry)
// 4) Symbol tidak bisa digabung otomatis dengan string
try {
console.log("Kunci: " + id1);
} catch (err) {
console.log(err.constructor.name); // → TypeError
}
console.log(`Kunci: ${id1.toString()}`); // → Kunci: Symbol(id)
// 5) Symbol.iterator: membuat object bisa di-loop
const tim = {
anggota: ["Rani", "Bayu", "Citra"],
[Symbol.iterator]() {
let i = 0;
const daftar = this.anggota;
return {
next: () => (i < daftar.length ? { value: daftar[i++], done: false } : { value: undefined, done: true }),
};
},
};
for (const orang of tim) console.log(orang);
// → Rani
// → Bayu
// → Citra
console.log([...tim]); // → [ 'Rani', 'Bayu', 'Citra' ]
// 6) Symbol.toPrimitive: mengatur konversi object
const uang = {
nominal: 25000,
[Symbol.toPrimitive](hint) {
// hint: "number", "string", atau "default"
return hint === "string" ? `Rp${this.nominal}` : this.nominal;
},
};
console.log(+uang); // → 25000
console.log(`${uang}`); // → Rp25000
console.log(uang + 5000); // → 30000
// 7) Symbol.toStringTag: label saat dicek tipenya
const dompet = { [Symbol.toStringTag]: "Dompet" };
console.log(Object.prototype.toString.call(dompet)); // → [object Dompet]
Kesalahan umum
- Mengira deskripsi menentukan identitas.
Symbol("x") === Symbol("x")bernilaifalse. Simpan Symbol di variabel dan pakai variabel itu, atau pakaiSymbol.forkalau memang butuh dibagi. - Memakai
new Symbol(). Symbol bukan constructor; tulisSymbol()saja. - Menganggap kunci Symbol itu privat. Kuncinya bisa ditemukan lewat
Object.getOwnPropertySymbols. Untuk data yang benar-benar privat, pakai closure atau field#privatdi class. - Kaget data hilang saat
JSON.stringify. Properti berkunci Symbol tidak ikut diserialisasi. Kalau datanya harus dikirim ke server, pakai kunci string. - Menggabungkan Symbol dengan string memakai
+. GunakanString(sym),sym.toString(), atausym.description.
Latihan
- Mudah, Buat dua Symbol dengan deskripsi
"token". Cetak apakah keduanya sama, lalu cetak deskripsi salah satunya. - Sedang, Buat object
produkdengan kunci stringnamadanharga, plus kunci SymbolSTOK_GUDANG. Tunjukkan bahwaObject.keysdanJSON.stringifytidak menampilkan stok, tapi kamu tetap bisa membacanya. - Menantang, Buat object
rentangdengan propertidaridansampai, lalu tambahkan[Symbol.iterator]sehingga[...rentang]menghasilkan semua angka daridarisampaisampai.
Lihat solusi
// 1) Mudah
const t1 = Symbol("token");
const t2 = Symbol("token");
console.log(t1 === t2); // → false
console.log(t1.description); // → token
// 2) Sedang
const STOK_GUDANG = Symbol("stokGudang");
const produk = { nama: "Gelas", harga: 15000, [STOK_GUDANG]: 240 };
console.log(Object.keys(produk)); // → [ 'nama', 'harga' ]
console.log(JSON.stringify(produk)); // → {"nama":"Gelas","harga":15000}
console.log(produk[STOK_GUDANG]); // → 240
// 3) Menantang
const rentang = {
dari: 3,
sampai: 7,
[Symbol.iterator]() {
let sekarang = this.dari;
const akhir = this.sampai;
return {
next() {
return sekarang <= akhir ? { value: sekarang++, done: false } : { value: undefined, done: true };
},
};
},
};
console.log([...rentang]); // → [ 3, 4, 5, 6, 7 ]
Ringkasan
- Symbol adalah tipe primitif yang dijamin unik; deskripsinya hanya label.
- Kunci Symbol tidak muncul di
Object.keys,for...in, maupunJSON.stringify, tapi bisa ditemukan denganObject.getOwnPropertySymbols. Symbol.for(kunci)memakai registry global sehingga Symbol yang sama bisa dibagi antar bagian kode.- Well-known symbols seperti
Symbol.iteratordanSymbol.toPrimitivemengatur perilaku bawaan object. - Symbol harus diubah ke string secara eksplisit; penggabungan dengan
+melemparTypeError.