Bab 13: Lain-Lain
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.
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:
| Trap | Kapan Dipanggil |
|---|---|
get | Baca properti |
set | Tulis properti |
has | Operator in |
deleteProperty | Operator delete |
apply | Panggil fungsi |
construct | Operator new |
ownKeys | Object.keys(), for..in |
Contoh: Nilai Default untuk Properti yang Tidak Ada
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
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 _)
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 tersembunyiReflect — 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".
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
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-revokeBerguna untuk: memberikan akses sementara ke data, lalu mencabutnya.
⚠️ Jebakan Proxy
- Built-in objects (Map, Set, Date) pakai "internal slot" yang nggak bisa di-intercept Proxy. Solusi: bind method ke target.
- Private fields (
#nama) juga nggak bisa di-proxy langsung. - Proxy ≠ target — mereka objek berbeda.
===nggak bisa di-intercept.
Buat fungsi wrap(target) yang mengembalikan Proxy. Kalau user baca properti yang TIDAK ADA di objek, throw Error alih-alih return undefined:
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!".
const kode = 'console.log("Halo dari eval!")';
eval(kode); // Output: Halo dari eval!eval menjalankan string sebagai kode JavaScript dan mengembalikan hasil statement terakhir:
const hasil = eval("2 + 2");
console.log(hasil); // 4
const x = eval("let a = 10; a * 2");
console.log(x); // 20Eval Bisa Akses Variabel Luar
let nama = "Budi";
eval('console.log(nama)'); // "Budi"
eval('nama = "Andi"');
console.log(nama); // "Andi" — variabel luar berubah!Strict Mode — Eval Punya Scope Sendiri
"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:
- Bahaya keamanan — kalau string-nya dari user input, bisa dieksploitasi (code injection)
- Performa buruk — engine nggak bisa optimasi kode di dalam eval
- Bikin minifier bingung — variabel lokal nggak bisa di-rename
Alternatif yang lebih aman:
// 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)); // 7Buat kalkulator sederhana yang menerima string ekspresi matematika dan mengembalikan hasilnya. Gunakan eval (ini satu-satunya kasus yang "boleh" untuk latihan):
function kalkulator(ekspresi) {
// kode kamu di sini
}
console.log(kalkulator("2 + 3 * 4")); // 14
console.log(kalkulator("(10 - 2) / 4")); // 213.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).
// 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)); // 5Kenapa Currying Berguna?
Currying memungkinkan kita membuat "fungsi spesialis" dari fungsi umum:
// 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 terhubungImplementasi Curry Universal
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- Currying hanya bekerja untuk fungsi dengan jumlah parameter tetap (nggak bisa pakai
...args) fungsi.lengthmengembalikan jumlah parameter yang dideklarasikan
Buat fungsi curry sendiri, lalu gunakan untuk membuat fungsi diskon:
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 = 10100013.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?
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:
user.sapa()→ JavaScript melihatuser.sapasebagai Reference Type(user, "sapa", false)→this=user✅const sapa = user.sapa→ assignment menghilangkan Reference Type → yang tersisa cuma fungsinya →thishilang ❌
Kasus Lain yang Bikin Bingung
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
const sapa = user.sapa.bind(user);
sapa(); // "Halo, saya Budi" ✅⚠️ Jebakan: Titik Koma!
let user = {
nama: "Budi",
go: function() { console.log(this.nama); }
}
(user.go)() // Error! Kenapa?Karena TIDAK ADA titik koma setelah }. JavaScript membaca ini sebagai:
let user = { ... }(user.go)()
// Dianggap: panggil objek sebagai fungsi!Selalu pakai titik koma!
Prediksi output dari kode berikut (tanpa menjalankannya):
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.
// 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
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!
// 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
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
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
Hitung faktorial dari 100 menggunakan BigInt (hasilnya punya 158 digit!):
function faktorial(n) {
// kode kamu di sini (pakai BigInt)
}
console.log(faktorial(100n));
// Harus menghasilkan angka yang diakhiri ...00000000000000000000000000n13.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.
// 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!
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
// 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 ✅- Jangan potong string sembarangan — bisa motong di tengah surrogate pair!
"string".lengthmenghitung unit UTF-16, bukan karakter visual- Untuk hitung karakter visual:
[...string].length
console.log("Hi 😂".length); // 5 (salah untuk "karakter")
console.log([..."Hi 😂"].length); // 4 (benar!)Buat fungsi yang menghitung jumlah emoji dalam sebuah string:
function hitungEmoji(str) {
// kode kamu di sini
// Hint: emoji punya codePoint > 0xFFFF
}
console.log(hitungEmoji("Halo 😂🚀 dunia 🌍")); // 313.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.
// 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 undefinedKapan Pakai WeakRef?
Kasus utama: cache yang nggak mau bikin memory leak.
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
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
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;
};
}- Nggak bisa diandalkan — kamu nggak tahu KAPAN garbage collector akan jalan
- Cleanup callback mungkin nggak dipanggil — misalnya kalau tab browser ditutup
- Jangan pakai untuk logic penting — hanya untuk optimasi (cache, monitoring)
- 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!
Jelaskan dengan kata-katamu sendiri: apa bedanya kode ini?
// Versi A
const cache = new Map();
cache.set("foto1", gambarBesar);
// Versi B
const cache = new Map();
cache.set("foto1", new WeakRef(gambarBesar));Pertanyaan:
- Di versi mana
gambarBesarbisa dihapus garbage collector meskipun masih ada di cache? - Di versi mana memory leak lebih mungkin terjadi?
- Kapan kamu pilih versi A vs versi B?
Ringkasan Bab 13
| Topik | Kegunaan Utama | Level |
|---|---|---|
| Proxy & Reflect | Intercept operasi objek, validasi, logging | Lanjutan |
| Eval | Jalankan string sebagai kode (HINDARI!) | Dasar tapi berbahaya |
| Currying | Buat fungsi spesialis dari fungsi umum | Menengah |
| Reference Type | Pahami kenapa this bisa hilang | Konseptual |
| BigInt | Angka sangat besar tanpa kehilangan presisi | Menengah |
| Unicode | Pahami internal string, emoji, normalisasi | Lanjutan |
| WeakRef | Cache yang nggak bikin memory leak | Sangat 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.