kodingup.camp

Beranda/JavaScript Mastery: Object & Data

Utilitas dan Operasi Koleksi

Modul 11 dari 145 menit bacaCollection Utilities & Operations

Isi modul

    Kenapa ini penting

    Setelah bisa menyimpan data dalam array, pekerjaan berikutnya adalah mengolahnya: menampilkan semua produk, mengubah harga dalam dolar menjadi rupiah, menyaring produk yang stoknya habis, menghitung total belanja, mencari pengguna berdasarkan email, memeriksa apakah ada pesanan yang belum dibayar, atau mengurutkan produk dari yang termurah.

    Semua tugas ini bisa dikerjakan dengan loop for biasa, tapi JavaScript menyediakan method array tingkat tinggi (higher-order methods: method yang menerima function sebagai argumen) yang membuat kode lebih pendek, lebih mudah dibaca, dan lebih sedikit bug. Method seperti map, filter, dan reduce adalah kosakata wajib programmer JavaScript modern. Kamu akan melihatnya di hampir setiap codebase, terutama di React untuk menampilkan daftar.

    Konsep inti

    Callback

    Semua method di modul ini menerima callback: function yang akan dipanggil untuk setiap elemen. Callback menerima tiga argumen: (elemen, indeks, arraynya). Biasanya kamu cukup memakai argumen pertama.

    Tabel method utama

    Method Tujuan Mengembalikan Mengubah asli?
    forEach menjalankan aksi per elemen undefined tidak (kecuali callback-nya mengubah)
    map mengubah setiap elemen array baru, panjang sama tidak
    filter menyaring elemen array baru, bisa lebih pendek tidak
    reduce meringkas jadi satu nilai nilai apa pun tidak
    find elemen pertama yang cocok elemen atau undefined tidak
    findIndex indeks pertama yang cocok angka atau -1 tidak
    findLast / findLastIndex sama, dicari dari belakang elemen/indeks tidak
    some adakah minimal satu yang cocok? true/false tidak
    every apakah semua cocok? true/false tidak
    sort mengurutkan array yang sama ya
    toSorted mengurutkan array baru tidak

    Analogi dapur: map seperti mesin pengupas yang mengupas setiap kentang (jumlah tetap, bentuk berubah). filter seperti saringan yang hanya meloloskan kentang yang bagus. reduce seperti blender yang menggabungkan semua jadi satu mangkuk sup.

    forEach vs map

    forEach dipakai untuk efek samping (mencetak, menyimpan ke tempat lain) dan tidak mengembalikan apa-apa. map dipakai untuk menghasilkan array baru. Jangan memakai map kalau kamu tidak memakai hasilnya. Satu hal lagi: forEach tidak bisa dihentikan di tengah dengan break. Kalau butuh berhenti lebih awal, pakai for...of, some, atau find.

    reduce

    arr.reduce((akumulator, elemen) => ..., nilaiAwal) berjalan dari kiri ke kanan. akumulator adalah "hasil sementara", dan apa pun yang dikembalikan callback menjadi akumulator untuk elemen berikutnya. Selalu beri nilai awal. Tanpa nilai awal, elemen pertama dipakai sebagai akumulator, dan pada array kosong reduce melempar TypeError.

    reduce bisa menghasilkan apa saja: angka (total), object (pengelompokan atau hitungan), atau array. Tapi kalau map atau filter sudah cukup, pakai yang lebih sederhana demi keterbacaan.

    some dan every

    some berhenti begitu menemukan satu yang cocok; every berhenti begitu menemukan satu yang tidak cocok. Pada array kosong, some mengembalikan false dan every mengembalikan true (secara logika, "semua elemen dari kumpulan kosong memenuhi syarat").

    Mengurutkan dengan comparator

    Tanpa argumen, sort() mengubah elemen menjadi string lalu mengurutkan berdasarkan urutan karakter Unicode. Itu sebabnya [10, 1, 5, 100].sort() menghasilkan [1, 10, 100, 5]. Untuk angka, berikan comparator (function pembanding) (a, b) => ...:

    Jadi (a, b) => a - b mengurutkan naik, (a, b) => b - a mengurutkan turun. Untuk teks, pakai a.localeCompare(b, "id").

    sort mengubah array asli. Kalau tidak ingin mengubahnya, pakai toSorted (ES2023) atau salin dulu [...arr].sort(...). Sejak ES2019, sort dijamin stabil: elemen yang dianggap sama tetap pada urutan aslinya, sehingga kamu bisa mengurutkan bertingkat dengan aman. Saudaranya, toReversed(), adalah versi tanpa mutasi dari reverse().

    Contoh kode

    const produk = [
      { nama: "Kaos Polos", kategori: "pakaian", harga: 75000, stok: 12 },
      { nama: "Topi Rajut", kategori: "aksesoris", harga: 45000, stok: 0 },
      { nama: "Jaket Denim", kategori: "pakaian", harga: 320000, stok: 4 },
      { nama: "Tas Selempang", kategori: "aksesoris", harga: 150000, stok: 7 },
      { nama: "Celana Chino", kategori: "pakaian", harga: 210000, stok: 0 },
    ];
    
    // 1) forEach: efek samping (mencetak)
    produk.forEach((p, i) => console.log(`${i + 1}. ${p.nama}`));
    // → 1. Kaos Polos
    // → 2. Topi Rajut
    // → 3. Jaket Denim
    // → 4. Tas Selempang
    // → 5. Celana Chino
    
    // 2) map: membentuk array baru
    const namaSaja = produk.map((p) => p.nama);
    console.log(namaSaja.length, namaSaja[2]); // → 5 Jaket Denim
    const labelHarga = produk.map((p) => `${p.nama}: Rp${p.harga.toLocaleString("id-ID")}`);
    console.log(labelHarga[0]); // → Kaos Polos: Rp75.000
    
    // 3) filter: menyaring
    const tersedia = produk.filter((p) => p.stok > 0);
    console.log(tersedia.map((p) => p.nama)); // → [ 'Kaos Polos', 'Jaket Denim', 'Tas Selempang' ]
    
    // 4) reduce: meringkas
    const nilaiInventaris = produk.reduce((total, p) => total + p.harga * p.stok, 0);
    console.log(nilaiInventaris); // → 3230000
    
    // reduce untuk menghitung jumlah per kategori (hasilnya object)
    const jumlahPerKategori = produk.reduce((hitung, p) => {
      hitung[p.kategori] = (hitung[p.kategori] ?? 0) + 1;
      return hitung;
    }, {});
    console.log(jumlahPerKategori); // → { pakaian: 3, aksesoris: 2 }
    
    try {
      [].reduce((a, b) => a + b); // tanpa nilai awal pada array kosong
    } catch (err) {
      console.log(err.constructor.name); // → TypeError
    }
    console.log([].reduce((a, b) => a + b, 0)); // → 0 (aman dengan nilai awal)
    
    // 5) find, findIndex, findLast
    console.log(produk.find((p) => p.harga > 200000).nama);        // → Jaket Denim
    console.log(produk.findIndex((p) => p.nama === "Topi Rajut")); // → 1
    console.log(produk.find((p) => p.harga > 1000000));            // → undefined
    console.log(produk.findLast((p) => p.stok === 0).nama);        // → Celana Chino
    
    // 6) some dan every
    console.log(produk.some((p) => p.stok === 0));    // → true  (ada yang habis)
    console.log(produk.every((p) => p.harga > 0));    // → true  (semua berharga)
    console.log([].some(Boolean), [].every(Boolean)); // → false true
    
    // 7) sort tanpa comparator: jebakan
    console.log([10, 1, 5, 100].sort());                // → [ 1, 10, 100, 5 ]
    console.log([10, 1, 5, 100].sort((a, b) => a - b)); // → [ 1, 5, 10, 100 ]
    
    // toSorted: urutkan tanpa mengubah asli
    const termurah = produk.toSorted((a, b) => a.harga - b.harga);
    console.log(termurah.map((p) => p.harga).join(", ")); // → 45000, 75000, 150000, 210000, 320000
    console.log(produk[0].nama);                          // → Kaos Polos (urutan asli tetap)
    
    // Urutkan teks sesuai bahasa Indonesia
    const namaUrut = produk.map((p) => p.nama).toSorted((a, b) => a.localeCompare(b, "id"));
    console.log(namaUrut[0], "|", namaUrut.at(-1)); // → Celana Chino | Topi Rajut
    
    // Urut bertingkat: kategori naik, lalu harga turun
    const bertingkat = produk.toSorted(
      (a, b) => a.kategori.localeCompare(b.kategori) || b.harga - a.harga
    );
    console.log(bertingkat.map((p) => p.nama).join(", "));
    // → Tas Selempang, Topi Rajut, Jaket Denim, Celana Chino, Kaos Polos
    
    // 8) Mengombinasikan: total harga produk pakaian yang tersedia
    const totalPakaian = produk
      .filter((p) => p.kategori === "pakaian" && p.stok > 0)
      .map((p) => p.harga)
      .reduce((a, b) => a + b, 0);
    console.log(totalPakaian); // → 395000
    

    Kesalahan umum

    1. Lupa return di callback dengan kurung kurawal. arr.map((x) => { x * 2 }) menghasilkan array berisi undefined. Tambahkan return, atau pakai bentuk ringkas (x) => x * 2.
    2. Memakai sort() tanpa comparator untuk angka. Hasilnya urutan teks. Selalu tulis (a, b) => a - b.
    3. Tidak sadar sort mengubah array asli. Data asli (misalnya dari state) ikut teracak. Pakai toSorted atau [...arr].sort(...).
    4. reduce tanpa nilai awal. Rawan TypeError pada array kosong dan hasil aneh pada array berisi object. Selalu beri nilai awal seperti 0, {}, atau [].
    5. Memakai filter(...)[0] untuk mencari satu elemen. filter memeriksa semua elemen; find berhenti di elemen pertama yang cocok dan lebih jelas maksudnya.

    Latihan

    1. Mudah, Diberikan const nilai = [55, 80, 72, 90, 64]. Buat array baru berisi nilai yang ditambah 5 poin, lalu saring yang di atas 75.
    2. Sedang, Diberikan array transaksi { jenis: "masuk" | "keluar", jumlah }. Hitung saldo akhir dengan reduce (masuk menambah, keluar mengurangi) dan cek dengan some apakah ada transaksi keluar di atas 1.000.000.
    3. Menantang, Diberikan array siswa { nama, kelas, nilai }. Buat daftar peringkat: urutkan berdasarkan nilai tertinggi; jika nilai sama, urutkan nama secara alfabet. Tampilkan sebagai teks "1. Nama (nilai)" tanpa mengubah array asli.
    Lihat solusi
    // 1) Mudah
    const nilai = [55, 80, 72, 90, 64];
    const hasil = nilai.map((n) => n + 5).filter((n) => n > 75);
    console.log(hasil); // → [ 85, 77, 95 ]
    
    // 2) Sedang
    const transaksi = [
      { jenis: "masuk", jumlah: 5000000 },
      { jenis: "keluar", jumlah: 1250000 },
      { jenis: "keluar", jumlah: 300000 },
      { jenis: "masuk", jumlah: 750000 },
    ];
    const saldo = transaksi.reduce((s, t) => (t.jenis === "masuk" ? s + t.jumlah : s - t.jumlah), 0);
    console.log(saldo); // → 4200000
    console.log(transaksi.some((t) => t.jenis === "keluar" && t.jumlah > 1000000)); // → true
    
    // 3) Menantang
    const siswa = [
      { nama: "Citra", kelas: "8A", nilai: 88 },
      { nama: "Bayu", kelas: "8A", nilai: 92 },
      { nama: "Andi", kelas: "8B", nilai: 88 },
      { nama: "Dewi", kelas: "8B", nilai: 75 },
    ];
    const peringkat = siswa
      .toSorted((a, b) => b.nilai - a.nilai || a.nama.localeCompare(b.nama, "id"))
      .map((s, i) => `${i + 1}. ${s.nama} (${s.nilai})`);
    console.log(peringkat);
    // → [ '1. Bayu (92)', '2. Andi (88)', '3. Citra (88)', '4. Dewi (75)' ]
    console.log(siswa[0].nama); // → Citra (asli tidak berubah)
    

    Ringkasan

    Kuis pemahaman

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