Kenapa ini penting
Selama ini kamu memakai object biasa sebagai "kamus" untuk menyimpan pasangan kunci-nilai: hitungan kata, harga per produk, pengaturan. Itu bekerja, tapi object biasa punya keterbatasan: kuncinya harus string atau Symbol, ia mewarisi properti dari prototype (yang bisa bertabrakan dengan kunci seperti "constructor"), dan tidak ada cara langsung untuk mengetahui jumlah isinya.
JavaScript menyediakan struktur data khusus untuk koleksi: Map (kamus dengan kunci tipe apa pun), Set (himpunan nilai unik), serta saudaranya WeakMap dan WeakSet yang dirancang supaya tidak menghalangi pembersihan memori. Memilih struktur yang tepat membuat kode lebih cepat, lebih aman, dan lebih jelas maksudnya. Ini adalah modul penutup materi Object & Data, sekaligus jembatan ke pola-pola lanjutan seperti caching dan pelacakan state.
Konsep inti
Map
Map menyimpan pasangan kunci-nilai dengan beberapa keunggulan dibanding object biasa:
- Kunci boleh tipe apa pun: angka, boolean, object, bahkan function. Kunci angka
1dan string"1"dianggap berbeda. - Urutan penyisipan selalu dipertahankan saat di-iterasi.
- Ukuran langsung tersedia lewat
map.size. - Iterable langsung: bisa dipakai di
for...ofdan spread. - Tidak ada kunci warisan, jadi tidak ada risiko tabrakan dengan
"constructor"atau"toString". - Dioptimalkan untuk penambahan dan penghapusan yang sering.
Method utama: set(k, v) (mengembalikan Map itu sendiri sehingga bisa dirangkai), get(k), has(k), delete(k), clear(), serta keys(), values(), entries(), dan forEach. Konversi: new Map(Object.entries(obj)) dan Object.fromEntries(map).
Analogi: object biasa seperti buku telepon yang hanya bisa diurutkan berdasarkan nama (string). Map seperti lemari penitipan yang kuncinya boleh berupa apa saja: kartu, gelang, atau token.
Peringatan: jangan menulis map["kunci"] = nilai. Itu menambah properti biasa pada object Map, bukan entri Map, sehingga tidak terdeteksi oleh get, has, atau size.
Set
Set adalah koleksi nilai unik. Menambahkan nilai yang sudah ada tidak berpengaruh. Method: add(v), has(v), delete(v), clear(), size. has pada Set jauh lebih cepat daripada includes pada array besar, karena Set tidak perlu memeriksa elemen satu per satu.
Keunikan dicek dengan algoritma SameValueZero: mirip ===, tapi NaN dianggap sama dengan NaN. Untuk object, yang dibandingkan referensinya.
ES2025 menambahkan method himpunan langsung pada Set: union (gabungan), intersection (irisan), difference (selisih), symmetricDifference, isSubsetOf, isSupersetOf, dan isDisjointFrom. Tersedia di Node.js 22 ke atas dan browser modern.
WeakMap dan WeakSet
Di JavaScript, memori dibersihkan otomatis oleh garbage collector (pengumpul sampah) ketika sebuah object tidak lagi dirujuk oleh siapa pun. Masalahnya, kalau kamu menyimpan object sebagai kunci di Map, Map itu tetap merujuknya, sehingga object tidak pernah dibersihkan walaupun bagian lain programmu sudah tidak memakainya. Ini disebut memory leak (kebocoran memori).
WeakMap dan WeakSet menyimpan rujukan lemah: kalau object kuncinya tidak dipakai di tempat lain, ia boleh dibersihkan dan entrinya ikut hilang. Konsekuensinya:
- Kunci WeakMap dan isi WeakSet harus object (atau Symbol yang tidak terdaftar di registry), bukan primitif.
- Tidak iterable dan tidak punya
size, karena isinya bisa hilang kapan saja. - Method yang tersedia hanya
get/set/has/delete(WeakMap) danadd/has/delete(WeakSet).
Kegunaan umum: menyimpan data tambahan atau cache untuk object yang kamu tidak "miliki" (misalnya elemen DOM atau object dari library), dan menandai object yang sudah diproses.
Kapan memakai Map vs object
| Situasi | Pilihan |
|---|---|
| Struktur tetap dengan field yang diketahui (data user, konfigurasi) | object |
| Data yang akan diubah ke JSON / dikirim ke API | object |
| Kunci berupa object, angka yang harus tetap angka, atau tipe campuran | Map |
Sering menambah/menghapus entri, butuh size |
Map |
| Kunci berasal dari input pengguna (risiko tabrakan prototype) | Map |
| Hanya perlu menyimpan nilai unik dan cek keberadaan | Set |
| Menyimpan data tambahan per object tanpa menghalangi garbage collection | WeakMap / WeakSet |
Satu catatan: JSON.stringify(map) menghasilkan "{}". Ubah dulu dengan Object.fromEntries(map) atau [...map] sebelum diserialisasi.
Contoh kode
// 1) Map dasar
const stok = new Map();
stok.set("kopi", 12).set("teh", 0).set("coklat", 5); // set bisa dirangkai
console.log(stok.get("kopi"), stok.has("teh"), stok.size); // → 12 true 3
stok.delete("teh");
console.log([...stok.keys()]); // → [ 'kopi', 'coklat' ]
console.log(stok.get("jus")); // → undefined
// 2) Kunci tipe apa pun
const m = new Map();
m.set(1, "angka satu");
m.set("1", "teks satu");
const userA = { nama: "Ana" };
m.set(userA, "data milik Ana");
console.log(m.get(1), "|", m.get("1"), "|", m.get(userA)); // → angka satu | teks satu | data milik Ana
console.log(m.get({ nama: "Ana" })); // → undefined (object berbeda referensi)
// 3) Iterasi dan konversi
const harga = new Map(Object.entries({ kopi: 18000, teh: 12000 }));
for (const [menu, rp] of harga) console.log(menu, rp);
// → kopi 18000
// → teh 12000
console.log(Object.fromEntries(harga)); // → { kopi: 18000, teh: 12000 }
console.log(JSON.stringify(harga)); // → {} (Map tidak terserialisasi langsung)
console.log(JSON.stringify(Object.fromEntries(harga))); // → {"kopi":18000,"teh":12000}
// 4) Jebakan: kurung siku bukan entri Map
const salah = new Map();
salah["kunci"] = "nilai";
console.log(salah.size, salah.has("kunci")); // → 0 false
// 5) Map aman untuk kunci dari input pengguna
const hitungObj = {};
const hitungMap = new Map();
for (const kata of ["constructor", "halo", "halo"]) {
hitungMap.set(kata, (hitungMap.get(kata) ?? 0) + 1);
}
console.log(typeof hitungObj["constructor"]); // → function (warisan prototype, bisa merusak hitungan)
console.log(hitungMap.get("constructor"), hitungMap.get("halo")); // → 1 2
// 6) Set dasar
const pengunjung = new Set();
pengunjung.add("ana").add("budi").add("ana");
console.log(pengunjung.size, pengunjung.has("budi")); // → 2 true
console.log(new Set([NaN, NaN, 0, -0]).size); // → 2 (NaN dianggap sama, 0 dan -0 juga)
// 7) Operasi himpunan (ES2025, Node 22+)
const kelasA = new Set(["Ani", "Budi", "Caca"]);
const kelasB = new Set(["Budi", "Dodi"]);
console.log([...kelasA.union(kelasB)]); // → [ 'Ani', 'Budi', 'Caca', 'Dodi' ]
console.log([...kelasA.intersection(kelasB)]); // → [ 'Budi' ]
console.log([...kelasA.difference(kelasB)]); // → [ 'Ani', 'Caca' ]
console.log(new Set(["Budi"]).isSubsetOf(kelasA)); // → true
// Cara klasik yang jalan di lingkungan lama
const irisanKlasik = [...kelasA].filter((n) => kelasB.has(n));
console.log(irisanKlasik); // → [ 'Budi' ]
// 8) WeakMap: data privat / cache per object
const dataPrivat = new WeakMap();
class Akun {
constructor(pemilik, pin) {
this.pemilik = pemilik;
dataPrivat.set(this, { pin }); // pin tidak terlihat sebagai properti
}
cekPin(pin) {
return dataPrivat.get(this).pin === pin;
}
}
const akun = new Akun("Dewi", "1234");
console.log(akun.cekPin("1234"), Object.keys(akun)); // → true [ 'pemilik' ]
try {
dataPrivat.set("teks", 1); // kunci primitif tidak boleh
} catch (err) {
console.log(err.constructor.name); // → TypeError
}
console.log(dataPrivat.size); // → undefined (WeakMap tidak punya size)
// Cache hasil perhitungan berat per object
const cacheTotal = new WeakMap();
function totalKeranjang(keranjang) {
if (cacheTotal.has(keranjang)) return `${cacheTotal.get(keranjang)} (cache)`;
const total = keranjang.items.reduce((s, i) => s + i.harga, 0);
cacheTotal.set(keranjang, total);
return `${total} (hitung)`;
}
const keranjangku = { items: [{ harga: 20000 }, { harga: 35000 }] };
console.log(totalKeranjang(keranjangku)); // → 55000 (hitung)
console.log(totalKeranjang(keranjangku)); // → 55000 (cache)
// 9) WeakSet: menandai object yang sudah diproses
const sudahDikirim = new WeakSet();
function kirimEmail(user) {
if (sudahDikirim.has(user)) return `Lewati ${user.email}`;
sudahDikirim.add(user);
return `Kirim ke ${user.email}`;
}
const u1 = { email: "a@mail.id" };
console.log(kirimEmail(u1)); // → Kirim ke a@mail.id
console.log(kirimEmail(u1)); // → Lewati a@mail.id
Kesalahan umum
- Memakai
map[kunci] = nilaialih-alihmap.set(kunci, nilai). Itu membuat properti biasa yang tidak terdeteksiget,has, atausize. - Mengharapkan
getmenemukan object dengan isi sama. Map membandingkan kunci object berdasarkan referensi. Simpan referensi object yang sama, atau pakai kunci primitif sepertiid. - Mengirim Map atau Set langsung ke
JSON.stringify. Hasilnya"{}". Ubah dulu denganObject.fromEntries(map)atau[...set]. - Mencoba me-loop atau membaca
sizeWeakMap/WeakSet. Keduanya sengaja tidak iterable. Kalau butuh daftar isi, pakai Map/Set biasa. - Memakai Map untuk segala hal. Untuk data berstruktur tetap yang dikirim sebagai JSON, object biasa lebih sederhana dan lebih umum dipakai.
Latihan
- Mudah, Buat
Setdari array["merah", "biru", "merah", "hijau", "biru"]. Cetak jumlah warna unik dan cek apakah"kuning"ada. - Sedang, Buat function
hitungFrekuensi(arr)yang mengembalikanMapberisi jumlah kemunculan setiap elemen, lalu ambil elemen yang paling sering muncul. - Menantang, Buat function
memo(fn)yang mengembalikan versi function dengan cache memakaiMap(kunci = argumen tunggal primitif). Hitung berapa kali function aslinya benar-benar dipanggil saatkuadratdipanggil dengan argumen4, 4, 5, 4.
Lihat solusi
// 1) Mudah
const warna = new Set(["merah", "biru", "merah", "hijau", "biru"]);
console.log(warna.size, warna.has("kuning")); // → 3 false
// 2) Sedang
function hitungFrekuensi(arr) {
const frek = new Map();
for (const item of arr) frek.set(item, (frek.get(item) ?? 0) + 1);
return frek;
}
const frek = hitungFrekuensi(["a", "b", "a", "c", "a", "b"]);
console.log(frek); // → Map(3) { 'a' => 3, 'b' => 2, 'c' => 1 }
const [terbanyak] = [...frek].reduce((maks, entri) => (entri[1] > maks[1] ? entri : maks));
console.log(terbanyak); // → a
// 3) Menantang
function memo(fn) {
const cache = new Map();
return (x) => {
if (!cache.has(x)) cache.set(x, fn(x));
return cache.get(x);
};
}
let jumlahPanggilan = 0;
const kuadrat = memo((n) => {
jumlahPanggilan++;
return n * n;
});
console.log([4, 4, 5, 4].map(kuadrat)); // → [ 16, 16, 25, 16 ]
console.log(jumlahPanggilan); // → 2
Ringkasan
Mapmenyimpan pasangan kunci-nilai dengan kunci tipe apa pun, menjaga urutan, punyasize, dan aman dari tabrakan prototype.Setmenyimpan nilai unik denganhasyang cepat; ES2025 menambahunion,intersection,difference, dan kawan-kawan.WeakMapdanWeakSethanya menerima object sebagai kunci/isi, tidak iterable, dan tidak menghalangi garbage collection.- Pakai object untuk data berstruktur tetap dan JSON; pakai
Mapuntuk kamus dinamis atau kunci non-string. - Map dan Set perlu dikonversi (
Object.fromEntries, spread) sebelumJSON.stringify.