for...of dan iterable

Iterasi modern atas array, string, dan Map dengan protokol iterable.

Daftar isi

for...of adalah bentuk perulangan modern yang mengambil isi koleksi satu per satu tanpa mengelola indeks. Topik ini mempraktikkannya atas array, string, Map, dan Set, lalu menjelaskan secara ringkas istilah iterable yang menjadi syaratnya. Bagian akhir membandingkan for...in dengan for...of, dua pernyataan yang mirip bentuknya tetapi berbeda tujuan, serta menutup dengan entries() dan spread untuk kebutuhan praktis.

for…of pada array dan string#

Pernyataan for...of mendeklarasikan satu variabel untuk menampung satu butir, lalu menelusuri koleksinya dari awal sampai habis. Tidak ada penghitung yang dirawat, tidak ada kondisi yang ditulis, dan tidak ada peluang meleset satu langkah. Bentuknya paling nyaman ketika isinya yang dibutuhkan, bukan posisinya.

js Contoh: menelusuri array belanja
const belanja = ["beras", "telur", "susu", "kopi"];

for (const butir of belanja) {
  console.log(`- ${butir}`);
}
text Keluaran
- beras
- telur
- susu
- kopi

Setiap putaran menyalin satu butir ke variabel butir, dan urutannya mengikuti urutan array. Kata const pada deklarasinya wajar dipakai karena setiap butir tidak diubah di dalam badan. Bila butir perlu diubah, pendekatan yang umum adalah menulis hasilnya ke array baru, bukan menimpa variabel penampungnya.

String juga bisa ditelusuri dengan for...of, dan setiap putaran memberikan satu karakter. Contoh berikut menghitung huruf vokal pada sebuah kata dengan memanfaatkan sifat itu.

js Contoh: menghitung huruf vokal per karakter
const kata = "durian";

let vokal = 0;

for (const huruf of kata) {
  if ("aiueo".includes(huruf)) {
    vokal += 1;
  }
}

console.log(`"${kata}": ${vokal} huruf vokal dari ${kata.length} huruf`);
text Keluaran
"durian": 3 huruf vokal dari 6 huruf

Kata durian memuat enam karakter, tiga di antaranya vokal, dan setiap karakter dibandingkan dengan isi string "aiueo". Metode includes() pada string memeriksa keberadaan satu karakter, sehingga badan perulangannya pendek. Untuk kata yang memuat karakter khusus, penelusuran per karakter dengan for...of mengikuti aturan Unicode, sedangkan nilai length bisa berbeda untuk karakter tertentu.

Map dan Set dalam for…of#

Map menyimpan pasangan kunci-nilai, dan for...of atas Map memberikan pasangan tersebut sebagai array dua elemen. Untuk menampungnya dengan rapi, gunakan destructuring, yakni membongkar isi array ke beberapa variabel sekaligus dalam satu baris. Pola [kunci, nilai] pada kepala for...of inilah yang dipakai di hampir setiap penelusuran Map.

js rekap-belanja.js
// Rekap daftar belanja pribadi
const belanja = ["beras", "telur", "susu", "kopi", "beras"];

const harga = new Map([
  ["beras", 68000],
  ["telur", 29000],
  ["susu", 18500],
  ["kopi", 24000],
]);

let total = 0;

for (const [butir, hargaSatuan] of harga) {
  total += hargaSatuan;
  console.log(`${butir}: Rp${hargaSatuan}`);
}

console.log(`Total: Rp${total}`);

const butirUnik = new Set(belanja);
console.log(`Tercatat ${belanja.length} butir, uniknya ${butirUnik.size} butir`);
text Keluaran
beras: Rp68000
telur: Rp29000
susu: Rp18500
kopi: Rp24000
Total: Rp139500
Tercatat 5 butir, uniknya 4 butir

Setiap baris keluaran berasal dari satu entri Map yang dibongkar menjadi butir dan hargaSatuan, sehingga penjumlahan total berjalan bersamaan dengan pencetakan. Set di baris terakhir bertindak sebagai penyaring keunikan, karena butir beras yang tertulis dua kali pada daftar hanya terhitung sekali. Ukuran Set terbaca dari properti size, berbeda dengan array yang memakai length. Pola destructuring yang sama berlaku untuk Map berisi tipe nilai apa pun, sehingga Map tetap bisa ditelusuri meskipun nilainya tidak ikut dijumlahkan.

Protokol iterable secara ringkas#

Tidak semua nilai bisa ditelusuri for...of. Syaratnya satu, nilai itu harus iterable, yakni menyediakan mekanisme bawaan untuk menghasilkan butirnya satu per satu. Array, string, Map, dan Set memenuhinya, sedangkan object biasa tidak. Mekanisme itu dibangun di atas simbol Symbol.iterator, yang cukup diketahui namanya di tahap ini karena pembuatan iterable sendiri jarang dibutuhkan.

js Contoh: for...of pada object biasa yang tidak iterable
const stok = { beras: 2, telur: 1 };

try {
  for (const butir of stok) {
    console.log(butir);
  }
} catch (err) {
  console.log(err.message);
}
text Keluaran
stok is not iterable

Pesan errornya menjelaskan persis apa yang kurang, yaitu stok tidak iterable. Error itu dilempar sebelum satu putaran pun berjalan, karena pengecekannya terjadi saat for...of dimulai. Penelusuran object memang memakai jalur yang berbeda, yaitu for...in, seperti dibahas pada bagian berikut.

for…in vs for…of#

Pernyataan for...in dan for...of mirip bentuknya, tetapi hasilnya berbeda. for...in menelusuri kunci, yaitu nama properti pada object, sedangkan for...of menelusuri isi koleksi yang iterable. Masing-masing punya kegunaan sendiri, dan kekeliruan terjadi ketika keduanya ditukar.

js Contoh: for...in pada object, tempat yang tepatnya
const stok = { beras: 2, telur: 1, susu: 3 };

for (const nama in stok) {
  console.log(`${nama}: ${stok[nama]}`);
}
text Keluaran
beras: 2
telur: 1
susu: 3

Pada object di atas, for...in memberikan nama properti satu per satu, dan nilainya diambil lewat stok[nama] dengan akses bracket. Bentuk inilah yang paling sering ditemui untuk object. Persoalan muncul ketika for...in dipakai pada array, sebagaimana diperagakan berikut ini.

js Contoh: for...in pada array dibanding for...of
const daftar = ["beras", "telur", "susu"];

// keliru: for...in pada array memberikan indeks dalam bentuk teks
for (const kunci in daftar) {
  console.log(`for...in -> ${kunci} (${typeof kunci})`);
}

// tepat: for...of memberikan isinya
for (const isi of daftar) {
  console.log(`for...of -> ${isi}`);
}
text Keluaran
for...in -> 0 (string)
for...in -> 1 (string)
for...in -> 2 (string)
for...of -> beras
for...of -> telur
for...of -> susu

Pada array, for...in memberikan indeks 0, 1, dan 2 dalam bentuk string, bukan isinya. Tipe string itu tercetak pada keluaran, dan kunci berupa teks berbahaya ketika dipakai untuk aritmetika atau perbandingan ketat. Selain itu, for...in juga ikut menelusuri properti tambahan yang menempel pada array, sesuatu yang jarang diinginkan. Karena itu, gunakan for...of untuk array dan for...in untuk object.

Perbedaan ini paling mudah terasa ketika dicoba langsung. Demo berikut menyediakan dua data, yaitu array dan object, beserta dua tombol. Pilih salah satu data, tekan salah satu tombol, lalu bandingkan apa yang keluar. Kombinasi for...of pada object akan langsung menampilkan pesan error yang sama dengan contoh sebelumnya.

entries() dan spread#

Dua perkakas kecil melengkapi for...of pada keadaan yang sering muncul. Yang pertama adalah entries() pada array, yang menyediakan pasangan indeks dan isi sekaligus. Dibongkar dengan destructuring, pola ini menggantikan for klasik ketika nomor urut dibutuhkan tanpa mengelola penghitung sendiri.

js Contoh: nomor urut dengan entries()
const daftar = ["beras", "telur", "susu"];

for (const [i, nama] of daftar.entries()) {
  console.log(`${i + 1}. ${nama}`);
}
text Keluaran
1. beras
2. telur
3. susu

Indeks dari entries() berupa angka sungguhan, sehingga penambahan i + 1 menghasilkan nomor urut yang benar. Untuk object, pasangan kunci-nilai disediakan oleh Object.entries(), sehingga object juga bisa ditelusuri lewat for...of dengan destructuring yang sama.

js Contoh: Object.entries() untuk pasangan kunci-nilai
const stok = { beras: 2, telur: 1, susu: 3 };

for (const [nama, banyak] of Object.entries(stok)) {
  console.log(`${nama} x${banyak}`);
}
text Keluaran
beras x2
telur x1
susu x3

Perkakas kedua adalah spread, yakni membentangkan isi koleksi dengan tiga titik. Di dalam kepala for...of, spread menyatukan beberapa koleksi menjadi satu urutan dalam satu ekspresi, tanpa variabel perantara yang harus diberi nama.

js Contoh: menggabungkan dua daftar dengan spread
const pagi = ["beras", "telur"];
const siang = ["susu", "kopi"];

for (const butir of [...pagi, ...siang]) {
  console.log(`- ${butir}`);
}
text Keluaran
- beras
- telur
- susu
- kopi

Kesalahan umum#

  1. Memakai for...of pada object biasa. Object tidak iterable, sehingga perulangannya langsung memunculkan TypeError sebelum satu putaran pun berjalan. Gunakan for...in untuk kunci atau Object.entries() untuk pasangan kunci-nilai.
  2. Mengubah koleksi yang sedang ditelusuri. Menambah elemen di tengah penelusuran memperpanjang perulangan, dan hasil akhirnya sulit diprediksi. Susun dulu daftar perubahannya ke wadah terpisah, lalu terapkan setelah perulangan selesai.
  3. Mengabaikan tipe kunci Map. Kunci Map dibandingkan dengan ===, sehingga kunci "1" berupa string dan 1 berupa angka tersimpan sebagai dua entri berbeda. Tentukan satu tipe kunci sejak awal agar penelusurannya konsisten.

Ringkasan#

  • for...of menelusuri isi koleksi iterable seperti array, string, Map, dan Set, tanpa penghitung maupun kondisi yang dirawat sendiri.
  • Entri Map dibongkar dengan destructuring [kunci, nilai], sedangkan Set menyaring butir ganda dan diukur dengan size.
  • for...in menelusuri kunci object, dan pada array memberikan indeks berupa string, sehingga array tetap ditelusuri dengan for...of.
  • entries() memberikan pasangan indeks-isi pada array dan kunci-nilai pada object lewat Object.entries(), sedangkan spread menggabungkan beberapa koleksi dengan tiga titik.

Referensi#