Budi, pemilik kafe kecil, menata papan pesanan dapur dalam satu object: nomor pesanan sebagai kunci, detail pesanan sebagai nilainya. Selama kunci berupa teks seperti "P-201", object bekerja dengan baik. Dua kebutuhan mulai terasa berat. Pertama, Budi ingin menjadikan object produk — bukan teks — sebagai kunci untuk menghitung jumlah terjual per produk. Kedua, menjawab pertanyaan “papan ini berisi berapa pesanan?” berarti menulis Object.keys(papan).length setiap kali.

JavaScript menyediakan struktur data yang dirancang untuk kebutuhan itu: Map. Map menyimpan pasangan kunci-nilai (key-value pair) dengan tiga kelebihan dibanding object: kunci boleh bertipe apa pun, jumlah pasangan dibaca lewat properti size, dan seluruh isi dapat ditelusuri langsung dalam urutan penyisipan. Chapter ini membangun papan pesanan kafe dengan Map, lalu menutup dengan dua function pengelompokan ES2024 — Object.groupBy dan Map.groupBy — yang masing-masing menghasilkan bentuk koleksi object dan Map.

Kunci Object yang Dipaksa Jadi String #

Aturan pada object: kunci hanya boleh berupa string atau symbol. Ketika kita memakai tipe lain sebagai kunci, runtime mengubahnya menjadi string lebih dulu. Object biasa dikonversi menjadi teks "[object Object]", dan angka 1 menjadi teks "1". Akibatnya, dua object berbeda yang dipakai sebagai kunci akan menimpa satu entri yang sama:

js
const harga = {};
const idProduk = { kode: 7 };
harga[idProduk] = 25000;
console.log(Object.keys(harga));

Output yang Dihasilkan:

text
[ '[object Object]' ]

Mari kita telaah baris-barisnya:

  1. Baris 2 (const harga = {}): object kosong disiapkan sebagai penampung harga.
  2. Baris 3 (const idProduk = { kode: 7 }): object produk dibuat; object inilah yang ingin dipakai sebagai kunci.
  3. Baris 4 (harga[idProduk] = 25000): runtime mengonversi idProduk menjadi string "[object Object]", lalu menyimpan 25000 pada kunci teks tersebut.
  4. Baris 5 (Object.keys(harga)): daftar kunci memperlihatkan satu kunci berupa teks '[object Object]' — object aslinya tidak tersimpan di mana pun.

Inilah batas pertama object sebagai koleksi kunci-nilai. Map tidak melakukan konversi seperti itu: kunci tetap pada tipe aslinya, termasuk object, angka, bahkan NaN.

Membuat Map dan Operasi Dasarnya #

Aturan Map: dibuat dengan new Map(), diisi lewat set(kunci, nilai), dibaca lewat get(kunci), diperiksa lewat has(kunci), dihapus lewat delete(kunci), dan dihitung lewat size. Di balik layar, Map menyimpan pasangan sesuai urutan penyisipan; set pada kunci yang sudah ada menggantikan nilai lama, bukan menambah entri baru. Constructor juga menerima array berisi pasangan [kunci, nilai] agar Map bisa terisi sejak awal. Berkas berikut membangun papan pesanan kafe sekaligus menghitung penjualan per produk dengan object sebagai kunci:

// papan-pesanan.js
// Papan pesanan kafe: nomor pesanan sebagai kunci, lalu rekap per produk.

const papan = new Map([
  ["P-201", { nama: "Siti", item: "Kopi Gayo", jumlah: 2 }],
  ["P-202", { nama: "Budi", item: "Teh Melati", jumlah: 1 }],
]);
console.log(papan.size);

papan.set("P-203", { nama: "Rina", item: "Kopi Gayo", jumlah: 1 });
console.log(papan.has("P-202"));
console.log(papan.get("P-201").nama);
console.log(papan.delete("P-203"));
console.log(papan.size);

// Kunci Map boleh object: menghitung jumlah terjual per produk
const kopiGayo = { nama: "Kopi Gayo" };
const tehMelati = { nama: "Teh Melati" };
const terjual = new Map();
terjual.set(kopiGayo, 3);
terjual.set(tehMelati, 1);
console.log(terjual.get(kopiGayo));
console.log(terjual.get({ nama: "Kopi Gayo" }));

// Rekap akhir hari: menelusuri seluruh pasangan kunci-nilai
for (const [nomor, pesanan] of papan) {
  console.log(`${nomor}: ${pesanan.nama} - ${pesanan.item}`);
}

Output yang Dihasilkan:

text
2
true
Siti
true
2
3
undefined
P-201: Siti - Kopi Gayo
P-202: Budi - Teh Melati

Mari kita telaah baris-baris pentingnya:

  1. Baris 4–7 (new Map([[...], [...]])): constructor menerima array yang tiap elemennya adalah pasangan [kunci, nilai]; dua pesanan terdaftar sejak Map dibuat.
  2. Baris 8 (console.log(papan.size)): size langsung menjawab jumlah pasangan — 2 — tanpa Object.keys().length.
  3. Baris 10 (papan.set("P-203", ...)): kunci "P-203" belum ada, sehingga set menambah entri baru ke ujung papan.
  4. Baris 11 (papan.has("P-202")): pemeriksaan keanggotaan kunci mengembalikan true.
  5. Baris 12 (papan.get("P-201").nama): get mengembalikan object pesanan utuh, dan akses .nama membaca salah satu propertinya.
  6. Baris 13 (papan.delete("P-203")): penghapusan mengembalikan true; baris 14 membuktikan size kembali menjadi 2.
  7. Baris 17–21 (terjual.set(kopiGayo, 3) dst): object kopiGayo dan tehMelati dipakai apa adanya sebagai kunci — tanpa konversi menjadi string.
  8. Baris 22 (terjual.get(kopiGayo)): pencarian memakai object yang sama menghasilkan 3.
  9. Baris 23 (terjual.get({ nama: "Kopi Gayo" })): object baru dengan isi serupa menghasilkan undefined — Map membandingkan kunci lewat identitas.
  10. Baris 26–28 (for...of atas papan): setiap putaran menghasilkan pasangan [kunci, nilai]; destructuring memisahkan keduanya, dan rekap mencetak dua pesanan tersisa.

Menelusuri Map dengan for-of #

Map dapat langsung ditelusuri karena mengikuti protokol iterasi yang dibahas di chapter berikutnya. Setiap putaran for...of menghasilkan array dua elemen berisi [kunci, nilai], sehingga pattern destructuring dari section Arrays menampung keduanya sekaligus:

js
const stok = new Map([["Kopi Gayo", 24], ["Teh Melati", 10]]);
for (const [nama, jumlah] of stok.entries()) {
  console.log(`${nama} sisa ${jumlah}`);
}

Output yang Dihasilkan:

text
Kopi Gayo sisa 24
Teh Melati sisa 10

Method entries() mengembalikan pasangan kunci-nilai; menelusuri stok tanpa entries() memberikan hasil yang sama karena itu perilaku bawaan Map. Untuk satu sisi saja, keys() mengembalikan kunci dan values() mengembalikan nilai — keduanya dibahas lebih lengkap di chapter for-of. Bila hasil penelusuran perlu diproses method array seperti map atau filter, kumpulkan dulu pasangannya dengan spread — [...papan.entries()] — sehingga menjadi array of array.

Object.groupBy dan Map.groupBy #

Aturan kedua function ES2024 ini: Object.groupBy(items, callback) dan Map.groupBy(items, callback) menjalankan callback untuk setiap elemen array, lalu mengelompokkan elemen berdasarkan nilai kembalian callback. Keduanya menghasilkan koleksi berbeda: Object.groupBy menghasilkan object, Map.groupBy menghasilkan Map. Perbedaan yang paling terasa ada pada kunci — kunci object selalu menjadi string, sedangkan kunci Map mempertahankan tipe aslinya:

js
const pesanan = [
  { kode: "P-301", kategori: "minuman" },
  { kode: "P-302", kategori: "makanan" },
  { kode: "P-303", kategori: "minuman" },
];

const perKategoriObjek = Object.groupBy(pesanan, (p) => p.kategori);
console.log(Object.keys(perKategoriObjek));
console.log(perKategoriObjek.minuman.length);

const perKategoriMap = Map.groupBy(pesanan, (p) => p.kategori);
console.log(perKategoriMap.get("makanan").map((p) => p.kode));

const nomor = [11, 12, 21, 22];
const kelompokMap = Map.groupBy(nomor, (n) => Math.floor(n / 10));
console.log(kelompokMap.get(1));
const kelompokObjek = Object.groupBy(nomor, (n) => Math.floor(n / 10));
console.log(Object.keys(kelompokObjek));

Output yang Dihasilkan:

text
[ 'minuman', 'makanan' ]
2
[ 'P-302' ]
[ 11, 12 ]
[ '1', '2' ]

Mari kita telaah hasilnya:

  1. Baris 7 (Object.groupBy(pesanan, ...)): hasilnya object dengan kunci "minuman" dan "makanan"; urutan kunci mengikuti kemunculan pertama di data.
  2. Baris 8–9: hasil pengelompokan adalah array elemen asli — perKategoriObjek.minuman.length bernilai 2, dan setiap anggotanya object pesanan lengkap.
  3. Baris 11–12 (Map.groupBy(...) + get): kelompok yang sama dapat dibaca lewat get("makanan"); callback (p) => p.kode memetakan isinya menjadi daftar kode.
  4. Baris 15–16 (Map.groupBy dengan kunci angka): callback mengembalikan 1 atau 2, dan kelompokMap.get(1) berhasil karena kunci Map tetap berupa angka.
  5. Baris 17–18 (Object.groupBy dengan kunci angka): Object.keys memperlihatkan '1' dan '2' — kunci object dipaksa menjadi string, sehingga membaca lewat angka 1 pun nyaris selalu memerlukan kehati-hatian ekstra.

Map menyelesaikan kebutuhan pasangan kunci-nilai dengan kunci bebas tipe. Kebutuhan sebaliknya — menyimpan sekumpulan nilai tanpa kunci dan menjamin tidak ada duplikat — ditangani struktur berikutnya: Set.

Berkas Sumber

Berkas pendukung halaman ini dapat diunduh dan dijalankan langsung sesuai penjelasan di atas.

papan-pesanan.js

Unduh berkas
javascript
// papan-pesanan.js
// Papan pesanan kafe: nomor pesanan sebagai kunci, lalu rekap per produk.

const papan = new Map([
  ["P-201", { nama: "Siti", item: "Kopi Gayo", jumlah: 2 }],
  ["P-202", { nama: "Budi", item: "Teh Melati", jumlah: 1 }],
]);
console.log(papan.size);

papan.set("P-203", { nama: "Rina", item: "Kopi Gayo", jumlah: 1 });
console.log(papan.has("P-202"));
console.log(papan.get("P-201").nama);
console.log(papan.delete("P-203"));
console.log(papan.size);

// Kunci Map boleh object: menghitung jumlah terjual per produk
const kopiGayo = { nama: "Kopi Gayo" };
const tehMelati = { nama: "Teh Melati" };
const terjual = new Map();
terjual.set(kopiGayo, 3);
terjual.set(tehMelati, 1);
console.log(terjual.get(kopiGayo));
console.log(terjual.get({ nama: "Kopi Gayo" }));

// Rekap akhir hari: menelusuri seluruh pasangan kunci-nilai
for (const [nomor, pesanan] of papan) {
  console.log(`${nomor}: ${pesanan.nama} - ${pesanan.item}`);
}