Bab 13: Lain-Lain

8 menit baca

Bab terakhir ini berisi topik-topik lanjutan yang nggak masuk kategori sebelumnya. Beberapa di antaranya jarang dipakai sehari-hari, tapi penting untuk dipahami kalau kamu mau jadi developer yang paham "dalamnya" JavaScript.


13.1 Proxy dan Reflect

Apa Itu Proxy?

Analogi: Bayangkan kamu punya rumah (objek). Proxy itu kayak satpam di depan rumah — setiap orang yang mau masuk, keluar, atau ngapa-ngapain, harus lewat satpam dulu. Satpam bisa izinkan, tolak, atau modifikasi permintaan.

javascript
const target = { nama: "Budi", umur: 25 };

const proxy = new Proxy(target, {
  get(target, prop) {
    console.log(`Seseorang membaca properti: ${prop}`);
    return target[prop];
  },
  set(target, prop, value) {
    console.log(`Seseorang mengubah ${prop} jadi ${value}`);
    target[prop] = value;
    return true; // WAJIB return true kalau berhasil
  }
});

proxy.nama;        // log: "Seseorang membaca properti: nama" → "Budi"
proxy.umur = 26;   // log: "Seseorang mengubah umur jadi 26"

Trap yang Tersedia

Proxy punya banyak "jebakan" (trap) yang bisa kamu pasang:

TrapKapan Dipanggil
getBaca properti
setTulis properti
hasOperator in
deletePropertyOperator delete
applyPanggil fungsi
constructOperator new
ownKeysObject.keys(), for..in

Contoh: Nilai Default untuk Properti yang Tidak Ada

javascript
const angka = [10, 20, 30];

const angkaAman = new Proxy(angka, {
  get(target, prop) {
    if (prop in target) {
      return target[prop];
    }
    return 0; // default kalau index nggak ada
  }
});

console.log(angkaAman[1]);   // 20
console.log(angkaAman[99]);  // 0 (bukan undefined!)

Contoh: Validasi Saat Set Nilai

javascript
const umurList = [];

const umurProxy = new Proxy(umurList, {
  set(target, prop, value) {
    if (typeof value !== "number" || value < 0) {
      throw new Error("Umur harus angka positif!");
    }
    target[prop] = value;
    return true;
  }
});

umurProxy.push(25);  // OK
umurProxy.push(30);  // OK
// umurProxy.push("abc"); // Error: Umur harus angka positif!

Contoh: Sembunyikan Properti Private (awalan _)

javascript
const user = {
  nama: "Andi",
  _password: "rahasia123"
};

const userAman = new Proxy(user, {
  get(target, prop) {
    if (prop.startsWith("_")) {
      throw new Error("Akses ditolak!");
    }
    return target[prop];
  },
  set(target, prop, value) {
    if (prop.startsWith("_")) {
      throw new Error("Akses ditolak!");
    }
    target[prop] = value;
    return true;
  },
  ownKeys(target) {
    return Object.keys(target).filter(key => !key.startsWith("_"));
  }
});

console.log(userAman.nama);      // "Andi"
// console.log(userAman._password); // Error: Akses ditolak!
console.log(Object.keys(userAman)); // ["nama"] — _password tersembunyi

Reflect — Partner Proxy

Reflect adalah objek bawaan yang punya method dengan nama yang SAMA persis dengan trap Proxy. Gunanya: meneruskan operasi ke objek asli dengan cara yang "benar".

javascript
const user = {
  _nama: "Guest",
  get nama() {
    return this._nama;
  }
};

const userProxy = new Proxy(user, {
  get(target, prop, receiver) {
    // Pakai Reflect.get supaya `this` tetap benar
    return Reflect.get(target, prop, receiver);
  }
});

const admin = {
  __proto__: userProxy,
  _nama: "Admin"
};

console.log(admin.nama); // "Admin" ✅ (tanpa Reflect, hasilnya "Guest")

Kenapa Reflect penting? Kalau kamu pakai target[prop] langsung di dalam trap get, dan ada inheritance (prototype chain), this bisa salah. Reflect.get meneruskan receiver yang benar.

Revocable Proxy — Proxy yang Bisa Dimatikan

javascript
const data = { rahasia: "kode nuklir" };

const { proxy, revoke } = Proxy.revocable(data, {});

console.log(proxy.rahasia); // "kode nuklir"

revoke(); // Matikan proxy!

// console.log(proxy.rahasia); // TypeError: proxy sudah di-revoke

Berguna untuk: memberikan akses sementara ke data, lalu mencabutnya.

⚠️ Jebakan Proxy

  1. Built-in objects (Map, Set, Date) pakai "internal slot" yang nggak bisa di-intercept Proxy. Solusi: bind method ke target.
  2. Private fields (#nama) juga nggak bisa di-proxy langsung.
  3. Proxy ≠ target — mereka objek berbeda. === nggak bisa di-intercept.
🎯Challenge

Buat fungsi wrap(target) yang mengembalikan Proxy. Kalau user baca properti yang TIDAK ADA di objek, throw Error alih-alih return undefined:

javascript
function wrap(target) {
  // kode kamu di sini
}

const obj = wrap({ nama: "Budi", umur: 25 });
console.log(obj.nama); // "Budi"
console.log(obj.umur); // 25
console.log(obj.alamat); // Error: Properti "alamat" tidak ada!

13.2 Eval — Menjalankan String sebagai Kode

Apa Itu Eval?

Analogi: eval itu kayak kamu nulis kode di kertas, terus kasih ke JavaScript dan bilang "jalankan ini sekarang!".

javascript
const kode = 'console.log("Halo dari eval!")';
eval(kode); // Output: Halo dari eval!

eval menjalankan string sebagai kode JavaScript dan mengembalikan hasil statement terakhir:

javascript
const hasil = eval("2 + 2");
console.log(hasil); // 4

const x = eval("let a = 10; a * 2");
console.log(x); // 20

Eval Bisa Akses Variabel Luar

javascript
let nama = "Budi";
eval('console.log(nama)'); // "Budi"

eval('nama = "Andi"');
console.log(nama); // "Andi" — variabel luar berubah!

Strict Mode — Eval Punya Scope Sendiri

javascript
"use strict";

eval("let x = 5;");
// console.log(x); // Error! x tidak terlihat di luar eval

⚠️ JANGAN PAKAI EVAL!

Serius. Hampir TIDAK ADA alasan untuk pakai eval di kode modern:

  1. Bahaya keamanan — kalau string-nya dari user input, bisa dieksploitasi (code injection)
  2. Performa buruk — engine nggak bisa optimasi kode di dalam eval
  3. Bikin minifier bingung — variabel lokal nggak bisa di-rename

Alternatif yang lebih aman:

javascript
// Kalau butuh jalankan kode di scope global:
// window.eval(kode) — di browser

// Kalau butuh buat fungsi dari string:
const tambah = new Function("a", "b", "return a + b");
console.log(tambah(3, 4)); // 7
🎯Challenge

Buat kalkulator sederhana yang menerima string ekspresi matematika dan mengembalikan hasilnya. Gunakan eval (ini satu-satunya kasus yang "boleh" untuk latihan):

javascript
function kalkulator(ekspresi) {
  // kode kamu di sini
}

console.log(kalkulator("2 + 3 * 4")); // 14
console.log(kalkulator("(10 - 2) / 4")); // 2

13.3 Currying

Apa Itu Currying?

Analogi: Bayangkan mesin pembuat kopi. Normalnya kamu masukkan semua bahan sekaligus (kopi, air, gula). Dengan currying, kamu masukkan satu-satu: pertama kopi → dapat mesin setengah jadi → masukkan air → dapat mesin hampir jadi → masukkan gula → dapat kopi!

Currying mengubah fungsi f(a, b, c) menjadi bisa dipanggil f(a)(b)(c).

javascript
// Fungsi biasa
function tambah(a, b) {
  return a + b;
}

// Versi curried (manual)
function tambahCurried(a) {
  return function(b) {
    return a + b;
  };
}

console.log(tambah(2, 3));         // 5
console.log(tambahCurried(2)(3));  // 5

Kenapa Currying Berguna?

Currying memungkinkan kita membuat "fungsi spesialis" dari fungsi umum:

javascript
// Fungsi log umum
function log(tanggal, level, pesan) {
  console.log(`[${tanggal.toLocaleTimeString()}] [${level}] ${pesan}`);
}

// Curry manual
function curriedLog(tanggal) {
  return function(level) {
    return function(pesan) {
      console.log(`[${tanggal.toLocaleTimeString()}] [${level}] ${pesan}`);
    };
  };
}

// Buat fungsi spesialis untuk log hari ini
const logHariIni = curriedLog(new Date());

// Buat fungsi lebih spesialis untuk debug hari ini
const debugHariIni = logHariIni("DEBUG");

// Pakai!
debugHariIni("Aplikasi dimulai");    // [14:30:00] [DEBUG] Aplikasi dimulai
debugHariIni("Database terhubung");  // [14:30:01] [DEBUG] Database terhubung

Implementasi Curry Universal

javascript
function curry(fungsi) {
  return function curried(...args) {
    // Kalau argumen sudah cukup, jalankan fungsi asli
    if (args.length >= fungsi.length) {
      return fungsi.apply(this, args);
    }
    // Kalau belum cukup, return fungsi yang nunggu argumen sisanya
    return function(...args2) {
      return curried.apply(this, args.concat(args2));
    };
  };
}

// Contoh penggunaan
function volume(panjang, lebar, tinggi) {
  return panjang * lebar * tinggi;
}

const curriedVolume = curry(volume);

// Semua cara ini valid:
console.log(curriedVolume(2, 3, 4));    // 24
console.log(curriedVolume(2)(3)(4));    // 24
console.log(curriedVolume(2, 3)(4));    // 24
console.log(curriedVolume(2)(3, 4));    // 24
⚠️Jebakan!
  • Currying hanya bekerja untuk fungsi dengan jumlah parameter tetap (nggak bisa pakai ...args)
  • fungsi.length mengembalikan jumlah parameter yang dideklarasikan
🎯Challenge

Buat fungsi curry sendiri, lalu gunakan untuk membuat fungsi diskon:

javascript
function hitungHarga(harga, pajak, diskon) {
  return harga + (harga * pajak) - diskon;
}

const curriedHarga = curry(hitungHarga);
const hargaDenganPPN = curriedHarga(100000)(0.11); // harga 100rb, PPN 11%

console.log(hargaDenganPPN(5000));  // 100000 + 11000 - 5000 = 106000
console.log(hargaDenganPPN(10000)); // 100000 + 11000 - 10000 = 101000

13.4 Reference Type

Apa Itu Reference Type?

Analogi: Waktu kamu bilang "tolong ambilkan buku di rak ketiga, baris kedua", kamu kasih referensi lengkap (rak + posisi). Tapi kalau kamu cuma bilang "ambilkan buku itu" sambil nunjuk, informasi "di mana"-nya bisa hilang.

Reference Type adalah tipe internal JavaScript yang menyimpan informasi: objek mana + properti apa + strict mode atau bukan.

Kenapa Ini Penting?

javascript
const user = {
  nama: "Budi",
  sapa() {
    console.log(`Halo, saya ${this.nama}`);
  }
};

// Ini bekerja — dot notation menjaga Reference Type
user.sapa(); // "Halo, saya Budi"

// Ini GAGAL — reference type hilang!
const sapa = user.sapa;
sapa(); // "Halo, saya undefined" (this = undefined di strict mode)

Apa yang terjadi di balik layar:

  1. user.sapa() → JavaScript melihat user.sapa sebagai Reference Type (user, "sapa", false)this = user
  2. const sapa = user.sapa → assignment menghilangkan Reference Type → yang tersisa cuma fungsinya → this hilang ❌

Kasus Lain yang Bikin Bingung

javascript
const user = {
  nama: "Budi",
  hi() { console.log(this.nama); },
  bye() { console.log("Bye"); }
};

// Ini gagal!
(user.nama === "Budi" ? user.hi : user.bye)(); // Error atau undefined

// Kenapa? Ternary operator menghilangkan Reference Type
// Sama seperti: let fn = user.hi; fn();

Solusi: Bind

javascript
const sapa = user.sapa.bind(user);
sapa(); // "Halo, saya Budi" ✅

⚠️ Jebakan: Titik Koma!

javascript
let user = {
  nama: "Budi",
  go: function() { console.log(this.nama); }
}

(user.go)() // Error! Kenapa?

Karena TIDAK ADA titik koma setelah }. JavaScript membaca ini sebagai:

javascript
let user = { ... }(user.go)()
// Dianggap: panggil objek sebagai fungsi!

Selalu pakai titik koma!

🎯Challenge

Prediksi output dari kode berikut (tanpa menjalankannya):

javascript
let obj = {
  go: function() { console.log(this); }
};

obj.go();                // ???
(obj.go)();              // ???
(obj.go || obj.go)();    // ???

13.5 BigInt

Apa Itu BigInt?

Analogi: Number biasa itu kayak kalkulator biasa — ada batas digit. BigInt itu kayak kertas tak terbatas — mau berapa digit pun bisa.

JavaScript Number hanya akurat sampai 2^53 - 1 (sekitar 9 quadrillion). Lebih dari itu? Pakai BigInt.

javascript
// Number biasa — kehilangan presisi!
console.log(9999999999999999);  // 10000000000000000 😱

// BigInt — presisi terjaga
console.log(9999999999999999n); // 9999999999999999n ✅

// Cara buat BigInt
const besar1 = 123456789012345678901234567890n; // tambah 'n' di akhir
const besar2 = BigInt("123456789012345678901234567890"); // dari string
const besar3 = BigInt(10); // dari number (sama dengan 10n)

Operasi Matematika

javascript
console.log(1n + 2n);   // 3n
console.log(5n / 2n);   // 2n (dibulatkan ke bawah, bukan 2.5!)
console.log(5n * 3n);   // 15n
console.log(10n - 7n);  // 3n
console.log(2n ** 10n); // 1024n

⚠️ Jebakan: Nggak Bisa Campur BigInt dan Number!

javascript
// console.log(1n + 2); // TypeError!

// Harus konversi dulu:
console.log(1n + BigInt(2)); // 3n
console.log(Number(1n) + 2); // 3

// Hati-hati: konversi BigInt besar ke Number kehilangan presisi!

Perbandingan

javascript
console.log(2n > 1n);   // true
console.log(2n > 1);    // true (bisa campur untuk perbandingan)
console.log(1n == 1);   // true (loose equality)
console.log(1n === 1);  // false! (tipe berbeda)

Boolean

javascript
if (0n) {
  // TIDAK akan dijalankan — 0n itu falsy
}

if (1n) {
  // AKAN dijalankan — selain 0n itu truthy
}

Kapan Pakai BigInt?

  • ID dari database yang sangat besar
  • Kriptografi
  • Timestamp presisi tinggi
  • Perhitungan keuangan yang butuh angka sangat besar
🎯Challenge

Hitung faktorial dari 100 menggunakan BigInt (hasilnya punya 158 digit!):

javascript
function faktorial(n) {
  // kode kamu di sini (pakai BigInt)
}

console.log(faktorial(100n));
// Harus menghasilkan angka yang diakhiri ...00000000000000000000000000n

13.6 Unicode dan String Internal

Bagaimana String Disimpan?

Analogi: Setiap karakter punya "nomor KTP" di dunia Unicode. JavaScript menyimpan string sebagai deretan nomor-nomor ini.

javascript
// Cara menulis karakter Unicode di JavaScript:

// 1. \xXX — 2 digit hex (hanya 256 karakter pertama)
console.log("\x7A");    // "z"
console.log("\xA9");    // "©"

// 2. \uXXXX — 4 digit hex (65536 karakter)
console.log("\u00A9");  // "©"
console.log("\u044F");  // "я" (huruf Rusia)
console.log("\u2191");  // "↑"

// 3. \u{X...X} — 1-6 digit hex (SEMUA karakter Unicode)
console.log("\u{1F60D}"); // 😍
console.log("\u{1F680}"); // 🚀

Surrogate Pairs — Emoji Itu 2 Karakter!

javascript
console.log("😂".length);  // 2 (bukan 1!)
console.log("🚀".length);  // 2

// Kenapa? Karena emoji butuh lebih dari 2 byte,
// jadi disimpan sebagai "surrogate pair" (pasangan 2 karakter)

// Akses per index bisa rusak:
console.log("😂"[0]); // karakter aneh (setengah pertama)
console.log("😂"[1]); // karakter aneh (setengah kedua)

// Cara benar: pakai codePointAt
console.log("😂".codePointAt(0).toString(16)); // 1f602

// Atau iterasi dengan for...of (surrogate-pair aware)
for (const char of "Hi 😂") {
  console.log(char); // "H", "i", " ", "😂" ✅
}

Normalisasi — Karakter yang "Sama" Tapi Beda

javascript
// Dua cara menulis "Ṩ":
const s1 = "S\u0307\u0323"; // S + titik atas + titik bawah
const s2 = "S\u0323\u0307"; // S + titik bawah + titik atas

console.log(s1); // Ṩ
console.log(s2); // Ṩ
console.log(s1 === s2); // false! 😱

// Solusi: normalize()
console.log(s1.normalize() === s2.normalize()); // true ✅
⚠️Jebakan!
  • Jangan potong string sembarangan — bisa motong di tengah surrogate pair!
  • "string".length menghitung unit UTF-16, bukan karakter visual
  • Untuk hitung karakter visual: [...string].length
javascript
console.log("Hi 😂".length);       // 5 (salah untuk "karakter")
console.log([..."Hi 😂"].length);  // 4 (benar!)
🎯Challenge

Buat fungsi yang menghitung jumlah emoji dalam sebuah string:

javascript
function hitungEmoji(str) {
  // kode kamu di sini
  // Hint: emoji punya codePoint > 0xFFFF
}

console.log(hitungEmoji("Halo 😂🚀 dunia 🌍")); // 3

13.7 WeakRef dan FinalizationRegistry

Apa Itu WeakRef?

Analogi: Referensi biasa (strong reference) itu kayak kamu pegang tali yang diikat ke balon — selama kamu pegang, balon nggak akan terbang. WeakRef itu kayak kamu cuma LIHAT balon — kalau nggak ada yang pegang talinya, balon bisa terbang kapan saja.

javascript
// Strong reference — objek TIDAK akan di-garbage collect
let user = { nama: "Budi" };

// Weak reference — objek BISA di-garbage collect
let weakUser = new WeakRef(user);

// Ambil objek dari WeakRef
console.log(weakUser.deref()); // { nama: "Budi" }

// Kalau strong reference dihapus...
user = null;

// ...suatu saat nanti, garbage collector akan hapus objeknya
// weakUser.deref() bisa return undefined

Kapan Pakai WeakRef?

Kasus utama: cache yang nggak mau bikin memory leak.

javascript
function buatCache(fetchData) {
  const cache = new Map();

  return (key) => {
    const cached = cache.get(key);

    // Kalau ada di cache DAN belum di-GC
    if (cached?.deref()) {
      console.log("Dari cache!");
      return cached.deref();
    }

    // Kalau nggak ada, fetch ulang
    console.log("Fetch baru!");
    const data = fetchData(key);
    cache.set(key, new WeakRef(data));
    return data;
  };
}

FinalizationRegistry — Notifikasi Saat Objek Dihapus

javascript
const registry = new FinalizationRegistry((info) => {
  console.log(`${info} sudah dihapus dari memori!`);
});

let objek = { data: "penting" };

// Daftarkan objek untuk di-track
registry.register(objek, "Objek Data Penting");

// Suatu saat nanti, setelah:
objek = null;
// ...garbage collector bekerja...
// Console: "Objek Data Penting sudah dihapus dari memori!"

Contoh Lengkap: Cache dengan Cleanup

javascript
function smartCache(fetchFn) {
  const cache = new Map();

  // Registry untuk bersihkan key yang sudah mati
  const registry = new FinalizationRegistry((key) => {
    const ref = cache.get(key);
    if (ref && !ref.deref()) {
      cache.delete(key); // Hapus key yang value-nya sudah di-GC
      console.log(`Cache "${key}" dibersihkan`);
    }
  });

  return (key) => {
    const cached = cache.get(key);

    if (cached?.deref()) {
      return cached.deref();
    }

    const value = fetchFn(key);
    cache.set(key, new WeakRef(value));
    registry.register(value, key); // Track untuk cleanup

    return value;
  };
}
⚠️Jebakan!
  1. Nggak bisa diandalkan — kamu nggak tahu KAPAN garbage collector akan jalan
  2. Cleanup callback mungkin nggak dipanggil — misalnya kalau tab browser ditutup
  3. Jangan pakai untuk logic penting — hanya untuk optimasi (cache, monitoring)
  4. Jarang dibutuhkan — kebanyakan kasus bisa diselesaikan tanpa WeakRef

Kapan TIDAK Pakai WeakRef?

  • Kalau kamu butuh data PASTI ada → pakai referensi biasa
  • Kalau kamu butuh cache yang reliable → pakai Map/LRU cache biasa
  • Kalau kamu baru belajar → fokus ke fitur lain dulu!
🎯Challenge

Jelaskan dengan kata-katamu sendiri: apa bedanya kode ini?

javascript
// Versi A
const cache = new Map();
cache.set("foto1", gambarBesar);

// Versi B
const cache = new Map();
cache.set("foto1", new WeakRef(gambarBesar));

Pertanyaan:

  1. Di versi mana gambarBesar bisa dihapus garbage collector meskipun masih ada di cache?
  2. Di versi mana memory leak lebih mungkin terjadi?
  3. Kapan kamu pilih versi A vs versi B?

Ringkasan Bab 13

TopikKegunaan UtamaLevel
Proxy & ReflectIntercept operasi objek, validasi, loggingLanjutan
EvalJalankan string sebagai kode (HINDARI!)Dasar tapi berbahaya
CurryingBuat fungsi spesialis dari fungsi umumMenengah
Reference TypePahami kenapa this bisa hilangKonseptual
BigIntAngka sangat besar tanpa kehilangan presisiMenengah
UnicodePahami internal string, emoji, normalisasiLanjutan
WeakRefCache yang nggak bikin memory leakSangat Lanjutan

🎉 Selamat!

Kamu sudah menyelesaikan SELURUH Part 1: The JavaScript Language dari javascript.info!

Dari variabel sederhana sampai Proxy dan WeakRef — perjalanan yang panjang. Kalau kamu paham semua materi ini, kamu sudah punya fondasi yang SANGAT kuat untuk:

  • Belajar framework (React, Vue, Svelte)
  • Belajar Node.js untuk backend
  • Belajar Part 2 (Browser: Document, Events, Interfaces)
  • Belajar Part 3 (Additional Articles)

Tips selanjutnya: Jangan cuma baca — KERJAKAN semua challenge! Pemahaman sejati datang dari praktek. 💪

Sudah paham materi ini?

Tandai sebagai selesai untuk melacak progress-mu.