kodingup.camp

Beranda/JavaScript Mastery: Object & Data

Konsep Pengenal Simbolik

Modul 5 dari 144 menit bacaSymbolic Identifier Concepts

Isi modul

    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:

    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

    1. Mengira deskripsi menentukan identitas. Symbol("x") === Symbol("x") bernilai false. Simpan Symbol di variabel dan pakai variabel itu, atau pakai Symbol.for kalau memang butuh dibagi.
    2. Memakai new Symbol(). Symbol bukan constructor; tulis Symbol() saja.
    3. Menganggap kunci Symbol itu privat. Kuncinya bisa ditemukan lewat Object.getOwnPropertySymbols. Untuk data yang benar-benar privat, pakai closure atau field #privat di class.
    4. Kaget data hilang saat JSON.stringify. Properti berkunci Symbol tidak ikut diserialisasi. Kalau datanya harus dikirim ke server, pakai kunci string.
    5. Menggabungkan Symbol dengan string memakai +. Gunakan String(sym), sym.toString(), atau sym.description.

    Latihan

    1. Mudah, Buat dua Symbol dengan deskripsi "token". Cetak apakah keduanya sama, lalu cetak deskripsi salah satunya.
    2. Sedang, Buat object produk dengan kunci string nama dan harga, plus kunci Symbol STOK_GUDANG. Tunjukkan bahwa Object.keys dan JSON.stringify tidak menampilkan stok, tapi kamu tetap bisa membacanya.
    3. Menantang, Buat object rentang dengan properti dari dan sampai, lalu tambahkan [Symbol.iterator] sehingga [...rentang] menghasilkan semua angka dari dari sampai sampai.
    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

    Kuis pemahaman

    Pilih satu jawaban per soal. Penjelasan muncul setelah kamu menjawab.