Bahasa Indonesia

Panduan komprehensif untuk memahami dan mengimplementasikan Protokol Iterator JavaScript, memberdayakan Anda untuk membuat iterator kustom untuk penanganan data yang lebih canggih.

Mengungkap Misteri Protokol Iterator dan Iterator Kustom pada JavaScript

Protokol Iterator JavaScript menyediakan cara standar untuk melintasi struktur data. Memahami protokol ini memberdayakan pengembang untuk bekerja secara efisien dengan iterable bawaan seperti array dan string, dan untuk membuat iterable kustom mereka sendiri yang disesuaikan dengan struktur data dan persyaratan aplikasi tertentu. Panduan ini memberikan eksplorasi komprehensif tentang Protokol Iterator dan cara mengimplementasikan iterator kustom.

Apa itu Protokol Iterator?

Protokol Iterator mendefinisikan bagaimana sebuah objek dapat diiterasi, yaitu, bagaimana elemen-elemennya dapat diakses secara berurutan. Ini terdiri dari dua bagian: protokol Iterable dan protokol Iterator.

Protokol Iterable

Sebuah objek dianggap Iterable jika memiliki sebuah metode dengan kunci Symbol.iterator. Metode ini harus mengembalikan sebuah objek yang sesuai dengan protokol Iterator.

Pada dasarnya, sebuah objek iterable tahu cara membuat iterator untuk dirinya sendiri.

Protokol Iterator

Protokol Iterator mendefinisikan cara mengambil nilai dari sebuah urutan. Sebuah objek dianggap sebagai iterator jika memiliki metode next() yang mengembalikan sebuah objek dengan dua properti:

Metode next() adalah komponen utama dari protokol Iterator. Setiap panggilan ke next() akan memajukan iterator dan mengembalikan nilai berikutnya dalam urutan. Ketika semua nilai telah dikembalikan, next() mengembalikan sebuah objek dengan done diatur ke true.

Iterable Bawaan

JavaScript menyediakan beberapa struktur data bawaan yang secara inheren dapat diiterasi. Ini termasuk:

Iterable ini dapat langsung digunakan dengan loop for...of, sintaksis spread (...), dan konstruksi lain yang mengandalkan Protokol Iterator.

Contoh dengan Array:


const myArray = ["apel", "pisang", "ceri"];

for (const item of myArray) {
  console.log(item); // Output: apel, pisang, ceri
}

Contoh dengan String:


const myString = "Hello";

for (const char of myString) {
  console.log(char); // Output: H, e, l, l, o
}

Loop for...of

Loop for...of adalah konstruksi yang kuat untuk melakukan iterasi pada objek iterable. Ini secara otomatis menangani kompleksitas Protokol Iterator, membuatnya mudah untuk mengakses nilai dalam sebuah urutan.

Sintaksis dari loop for...of adalah:


for (const element of iterable) {
  // Kode yang akan dieksekusi untuk setiap elemen
}

Loop for...of mengambil iterator dari objek iterable (menggunakan Symbol.iterator), dan berulang kali memanggil metode next() dari iterator sampai done menjadi true. Untuk setiap iterasi, variabel element diberi nilai dari properti value yang dikembalikan oleh next().

Membuat Iterator Kustom

Meskipun JavaScript menyediakan iterable bawaan, kekuatan sebenarnya dari Protokol Iterator terletak pada kemampuannya untuk mendefinisikan iterator kustom untuk struktur data Anda sendiri. Ini memungkinkan Anda untuk mengontrol bagaimana data Anda dilintasi dan diakses.

Berikut cara membuat iterator kustom:

  1. Definisikan sebuah kelas atau objek yang merepresentasikan struktur data kustom Anda.
  2. Implementasikan metode Symbol.iterator pada kelas atau objek Anda. Metode ini harus mengembalikan objek iterator.
  3. Objek iterator harus memiliki metode next() yang mengembalikan objek dengan properti value dan done.

Contoh: Membuat Iterator untuk Rentang Sederhana

Mari kita buat sebuah kelas bernama Range yang merepresentasikan rentang angka. Kita akan mengimplementasikan Protokol Iterator untuk memungkinkan iterasi pada angka-angka dalam rentang tersebut.


class Range {
  constructor(start, end) {
    this.start = start;
    this.end = end;
  }

  [Symbol.iterator]() {
    let currentValue = this.start;
    const that = this; // Menangkap 'this' untuk digunakan di dalam objek iterator

    return {
      next() {
        if (currentValue <= that.end) {
          return {
            value: currentValue++,
            done: false,
          };
        } else {
          return {
            value: undefined,
            done: true,
          };
        }
      },
    };
  }
}

const myRange = new Range(1, 5);

for (const number of myRange) {
  console.log(number); // Output: 1, 2, 3, 4, 5
}

Penjelasan:

Contoh: Membuat Iterator untuk Linked List

Mari kita pertimbangkan contoh lain: membuat iterator untuk struktur data linked list. Linked list adalah urutan node, di mana setiap node berisi nilai dan referensi (pointer) ke node berikutnya dalam daftar. Node terakhir dalam daftar memiliki referensi ke null (atau undefined).


class LinkedListNode {
    constructor(value, next = null) {
        this.value = value;
        this.next = next;
    }
}

class LinkedList {
    constructor() {
        this.head = null;
    }

    append(value) {
        const newNode = new LinkedListNode(value);
        if (!this.head) {
            this.head = newNode;
            return;
        }

        let current = this.head;
        while (current.next) {
            current = current.next;
        }
        current.next = newNode;
    }

    [Symbol.iterator]() {
        let current = this.head;

        return {
            next() {
                if (current) {
                    const value = current.value;
                    current = current.next;
                    return {
                        value: value,
                        done: false
                    };
                } else {
                    return {
                        value: undefined,
                        done: true
                    };
                }
            }
        };
    }
}

// Contoh Penggunaan:
const myList = new LinkedList();
myList.append("London");
myList.append("Paris");
myList.append("Tokyo");

for (const city of myList) {
    console.log(city); // Output: London, Paris, Tokyo
}

Penjelasan:

Fungsi Generator

Fungsi generator menyediakan cara yang lebih ringkas dan elegan untuk membuat iterator. Mereka menggunakan kata kunci yield untuk menghasilkan nilai sesuai permintaan.

Sebuah fungsi generator didefinisikan menggunakan sintaksis function*.

Contoh: Membuat Iterator menggunakan Fungsi Generator

Mari kita tulis ulang iterator Range menggunakan fungsi generator:


class Range {
  constructor(start, end) {
    this.start = start;
    this.end = end;
  }

  *[Symbol.iterator]() {
    for (let i = this.start; i <= this.end; i++) {
      yield i;
    }
  }
}

const myRange = new Range(1, 5);

for (const number of myRange) {
  console.log(number); // Output: 1, 2, 3, 4, 5
}

Penjelasan:

Fungsi generator menyederhanakan pembuatan iterator dengan menangani metode next() dan flag done secara otomatis.

Contoh: Generator Urutan Fibonacci

Contoh bagus lainnya dari penggunaan fungsi generator adalah menghasilkan urutan Fibonacci:


function* fibonacciSequence() {
  let a = 0;
  let b = 1;

  while (true) {
    yield a;
    [a, b] = [b, a + b]; // Penugasan destrukturisasi untuk pembaruan simultan
  }
}

const fibonacci = fibonacciSequence();

for (let i = 0; i < 10; i++) {
  console.log(fibonacci.next().value); // Output: 0, 1, 1, 2, 3, 5, 8, 13, 21, 34
}

Penjelasan:

Manfaat Menggunakan Protokol Iterator

Teknik Iterator Tingkat Lanjut

Menggabungkan Iterator

Anda dapat menggabungkan beberapa iterator menjadi satu iterator tunggal. Ini berguna ketika Anda perlu memproses data dari beberapa sumber secara terpadu.


function* combineIterators(...iterables) {
  for (const iterable of iterables) {
    for (const item of iterable) {
      yield item;
    }
  }
}

const array1 = [1, 2, 3];
const array2 = ["a", "b", "c"];
const string1 = "XYZ";

const combined = combineIterators(array1, array2, string1);

for (const value of combined) {
  console.log(value); // Output: 1, 2, 3, a, b, c, X, Y, Z
}

Dalam contoh ini, fungsi `combineIterators` menerima sejumlah iterable sebagai argumen. Ia melakukan iterasi pada setiap iterable dan menghasilkan (yield) setiap item. Hasilnya adalah satu iterator tunggal yang menghasilkan semua nilai dari semua iterable input.

Memfilter dan Mentransformasi Iterator

Anda juga dapat membuat iterator yang memfilter atau mentransformasi nilai yang dihasilkan oleh iterator lain. Ini memungkinkan Anda untuk memproses data dalam sebuah alur (pipeline), menerapkan operasi yang berbeda pada setiap nilai saat ia dihasilkan.


function* filterIterator(iterable, predicate) {
  for (const item of iterable) {
    if (predicate(item)) {
      yield item;
    }
  }
}

function* mapIterator(iterable, transform) {
  for (const item of iterable) {
    yield transform(item);
    }
}

const numbers = [1, 2, 3, 4, 5, 6];

const evenNumbers = filterIterator(numbers, (x) => x % 2 === 0);
const squaredEvenNumbers = mapIterator(evenNumbers, (x) => x * x);

for (const value of squaredEvenNumbers) {
    console.log(value); // Output: 4, 16, 36
}

Di sini, `filterIterator` menerima sebuah iterable dan fungsi predikat. Ia hanya menghasilkan item yang predikatnya mengembalikan `true`. `mapIterator` menerima sebuah iterable dan fungsi transformasi. Ia menghasilkan hasil dari penerapan fungsi transformasi pada setiap item.

Aplikasi di Dunia Nyata

Protokol Iterator banyak digunakan dalam pustaka dan kerangka kerja JavaScript, dan sangat berharga dalam berbagai aplikasi dunia nyata, terutama saat berurusan dengan kumpulan data besar atau operasi asinkron.

Praktik Terbaik

Kesimpulan

Protokol Iterator JavaScript menyediakan cara yang kuat dan fleksibel untuk melintasi struktur data. Dengan memahami protokol Iterable dan Iterator, dan dengan memanfaatkan fungsi generator, Anda dapat membuat iterator kustom yang disesuaikan dengan kebutuhan spesifik Anda. Ini memungkinkan Anda untuk bekerja secara efisien dengan data, meningkatkan keterbacaan kode, dan meningkatkan kinerja aplikasi Anda. Menguasai iterator membuka pemahaman yang lebih mendalam tentang kemampuan JavaScript dan memberdayakan Anda untuk menulis kode yang lebih elegan dan efisien.