Iterabel dan Iterator: Panduan Mendalam tentang JavaScript



Artikel ini adalah pengantar mendalam tentang iterable dan iterator di JavaScript. Motivasi utama saya untuk menulisnya adalah mempersiapkan diri untuk belajar tentang generator. Sebenarnya, saya berencana untuk bereksperimen nanti dengan menggabungkan generator dan kait React. Jika Anda tertarik, ikuti Twitter atau YouTube saya !



Sebenarnya, saya berencana untuk memulai dengan artikel tentang generator, tetapi segera menjadi jelas bahwa mereka sulit untuk dibicarakan tanpa pemahaman yang baik tentang iterable dan iterator. Kami akan fokus pada mereka sekarang. Saya akan berasumsi bahwa Anda tidak tahu apa-apa tentang topik ini, tetapi pada saat yang sama kami akan mempelajarinya secara signifikan. Jadi jika Anda adalah sesuatu tahu tentang iterable dan iterator, tetapi jangan merasa nyaman menggunakannya, artikel ini akan membantu Anda.



pengantar



Seperti yang Anda perhatikan, kami membahas iterable dan iterator. Ini adalah konsep yang saling terkait, tetapi berbeda, jadi ketika membaca artikel, perhatikan mana yang sedang dibahas dalam kasus tertentu.



Mari kita mulai dengan objek yang dapat diulang. Apa itu? Ini adalah sesuatu yang dapat diulang, misalnya:



for (let element of iterable) {
    // do something with an element
}

      
      





Harap dicatat bahwa kami hanya melihat loop for ... of



yang diperkenalkan di ES6 di sini. Dan loop for ... in



adalah konstruksi lama yang tidak akan kami rujuk sama sekali di artikel ini.



Sekarang Anda mungkin berpikir, "Oke, variabel yang dapat diulang ini hanyalah sebuah larik!" Benar, array dapat diulang. Tapi sekarang ada struktur lain dalam JavaScript asli yang dapat digunakan dalam satu putaran for ... of



. Artinya, selain array, ada objek iterable lainnya.



Misalnya, kita bisa mengulang Map



, diperkenalkan di ES6:



const ourMap = new Map();

ourMap.set(1, 'a');
ourMap.set(2, 'b');
ourMap.set(3, 'c');

for (let element of ourMap) {
    console.log(element);
}

      
      





Kode ini akan menampilkan:



[1, 'a']
[2, 'b']
[3, 'c']

      
      





Artinya, variabel element



pada setiap tahap iterasi menyimpan larik dua elemen. Yang pertama adalah kuncinya, yang kedua adalah nilainya.



Bahwa kami dapat menggunakan loop for ... of



untuk mengulang Map



membuktikan bahwa Map



itu dapat diulang. Sekali lagi for ... of



, hanya objek iterable yang dapat digunakan dalam loop . Artinya, jika sesuatu bekerja dengan perulangan ini, maka itu adalah objek yang dapat berulang.



Lucu bahwa konstruktor Map



secara opsional menerima iterabel dari pasangan nilai kunci. Artinya, ini adalah cara alternatif untuk membangun yang sama Map



:



const ourMap = new Map([
    [1, 'a'],
    [2, 'b'],
    [3, 'c'],
]);

      
      





Dan karena Map



ini merupakan iterable, kita dapat membuat salinannya dengan sangat mudah:



const copyOfOurMap = new Map(ourMap);

      
      





Kami sekarang memiliki dua yang berbeda Map



, meskipun mereka menyimpan kunci yang sama dengan nilai yang sama.



Jadi, kami melihat dua contoh objek iterable - array dan ES6 Map



. Tapi kita belum tahu bagaimana mereka bisa menjadi iterable. Jawabannya sederhana: ada iterator yang terkait dengannya . Hati-hati: iterator yang tidak iterable .



Bagaimana iterator dikaitkan dengan objek yang dapat berulang? Objek yang dapat di-iterable harus berisi fungsi di propertinya Symbol.iterator



. Saat dipanggil, fungsi tersebut harus mengembalikan iterator untuk objek ini.



Misalnya, Anda dapat mengambil iterator array:



const ourArray = [1, 2, 3];

const iterator = ourArray[Symbol.iterator]();

console.log(iterator);

      
      





Kode ini dikeluarkan ke konsol Object [Array Iterator] {}



. Sekarang kita tahu bahwa array memiliki iterator terkait, yang merupakan sejenis objek.



Apa itu iterator?



Itu mudah. Iterator adalah objek yang berisi metode next



. Ketika metode ini dipanggil, itu harus mengembalikan:



  • nilai berikutnya dalam urutan nilai;
  • informasi tentang apakah iterator telah selesai menghasilkan nilai.


Mari kita uji ini dengan memanggil metode next



pada iterator array kita:



const result = iterator.next();

console.log(result);

      
      





Kami akan melihat objek di konsol { value: 1, done: false }



. Elemen pertama dari array yang kita buat adalah 1, dan di sini muncul sebagai nilai. Kami juga menerima informasi bahwa iterator belum selesai, yaitu, kami masih dapat memanggil fungsi next



dan mendapatkan beberapa nilai. Mari mencoba! Mari kita menyebutnya next



dua kali lagi:



console.log(iterator.next());
console.log(iterator.next());

      
      





Diterima satu per satu { value: 2, done: false }



dan { value: 3, done: false }



.



Hanya ada tiga elemen dalam larik kami. Apa yang terjadi jika Anda meneleponnya lagi next



?



console.log(iterator.next());

      
      





Kali ini kita akan lihat { value: undefined, done: true }



. Ini menunjukkan bahwa iterator sudah selesai. Tidak ada gunanya menelepon lagi next



. Jika kita melakukan ini, berulang kali kita akan menerima sebuah benda { value: undefined, done: true }



. done: true



berarti menghentikan pengulangan.



Sekarang Anda dapat memahami apa yang dilakukannya di for ... of



bawah tenda:



  • metode pertama [Symbol.iterator]()



    dipanggil untuk mendapatkan iterator;
  • metode next



    ini dipanggil secara siklis di iterator sampai kita mendapatkannya done: true



    ;
  • setelah setiap panggilan next



    , properti digunakan di badan loop value



    .


Mari kita tulis semua ini dalam kode:



const iterator = ourArray[Symbol.iterator]();

let result = iterator.next();

while (!result.done) {
    const element = result.value;

    // do some something with element

    result = iterator.next();
}

      
      





Kode ini setara dengan ini:



for (let element of ourArray) {
    // do something with element
}

      
      





Anda dapat memverifikasi ini, misalnya, dengan menyisipkan console.log(element)



sebagai ganti komentar // do something with element



.



Buat iterator Anda sendiri



Kami sekarang tahu apa itu iterable dan iterator. Timbul pertanyaan: "Dapatkah saya menulis contoh saya sendiri?"



Pasti!



Tidak ada yang misterius tentang iterator. Ini hanyalah objek dengan metode next



yang berperilaku dengan cara khusus. Kami sudah menemukan nilai asli mana di JS yang dapat diulang. Tidak ada objek yang disebutkan di antara mereka. Memang, mereka tidak diulangi secara native. Pertimbangkan objek seperti ini:



const ourObject = {
    1: 'a',
    2: 'b',
    3: 'c'
};

      
      





Jika kita mengulanginya for (let element of ourObject)



, kita mendapatkan kesalahan object is not iterable



.



Mari kita tulis iterator kita sendiri dengan membuat objek seperti itu dapat diulang!



Untuk melakukan ini, Anda harus menambal prototipe Object



dengan metode Anda sendiri [Symbol.iterator]()



. Karena menambal prototipe adalah praktik yang buruk, mari buat kelas kita sendiri dengan memperluas Object



:



class IterableObject extends Object {
    constructor(object) {
        super();
        Object.assign(this, object);
    }
}

      
      





Konstruktor kelas kita mengambil objek biasa dan menyalin propertinya ke dalam objek yang dapat diulang (meskipun sebenarnya belum dapat diulang!).



Mari buat objek yang dapat berulang:



const iterableObject = new IterableObject({
    1: 'a',
    2: 'b',
    3: 'c'
})

      
      





Untuk membuat kelas IterableObject



benar-benar dapat diulang, kita membutuhkan sebuah metode [Symbol.iterator]()



. Mari tambahkan.



class IterableObject extends Object {
    constructor(object) {
        super();
        Object.assign(this, object);
    }

    [Symbol.iterator]() {

    }
}

      
      





Sekarang Anda dapat menulis iterator nyata!



Kita sudah tahu bahwa itu pasti objek dengan metode next



. Mari kita mulai dengan ini.



class IterableObject extends Object {
    // same as before

    [Symbol.iterator]() {
        return {
            next() {}
        }
    }
}

      
      





Setelah setiap panggilan, next



Anda perlu mengembalikan objek tampilan { value, done }



. Mari kita buat dengan nilai fiktif.



class IterableObject extends Object {
    // same as before

    [Symbol.iterator]() {
        return {
            next() {
                return {
                    value: undefined,
                    done: false
                }
            }
        }
    }
}

      
      





Diberikan objek iterable seperti ini:



const iterableObject = new IterableObject({
    1: 'a',
    2: 'b',
    3: 'c'
})

      
      





kami akan menampilkan pasangan nilai kunci, mirip dengan cara iterasi ES6 Map



:



['1', 'a']
['2', 'b']
['3', 'c']

      
      





Di iterator kami, property



kami akan menyimpan array di nilainya [key, valueForThatKey]



. Harap dicatat bahwa ini adalah solusi kami sendiri dibandingkan dengan langkah sebelumnya. Jika kita ingin menulis iterator yang hanya mengembalikan kunci atau hanya nilai properti, maka kita bisa melakukannya tanpa masalah. Kami baru saja memutuskan untuk mengembalikan pasangan nilai kunci sekarang.



Kami membutuhkan array tipe [key, valueForThatKey]



. Cara termudah untuk mendapatkannya adalah dengan metode tersebut Object.entries



. Kita bisa menggunakannya tepat sebelum membuat objek iterator dengan metode [Symbol.iterator]()



:



class IterableObject extends Object {
    // same as before

    [Symbol.iterator]() {
        // we made an addition here
        const entries = Object.entries(this);

        return {
            next() {
                return {
                    value: undefined,
                    done: false
                }
            }
        }
    }
}

      
      





Iterator yang dikembalikan dalam metode akan mengakses variabel berkat penutupan JavaScript entries



.



Kami juga membutuhkan variabel negara. Ini akan memberi tahu kami pasangan nilai kunci mana yang harus dikembalikan pada panggilan berikutnya next



. Mari tambahkan:



class IterableObject extends Object {
    // same as before

    [Symbol.iterator]() {
        const entries = Object.entries(this);
        // we made an addition here
        let index = 0;

        return {
            next() {
                return {
                    value: undefined,
                    done: false
                }
            }
        }
    }
}

      
      





Perhatikan bahwa kami mendeklarasikan variabel index



c let



karena kami tahu bahwa kami berencana untuk memperbarui nilainya setelah setiap panggilan next



.



Kami sekarang siap untuk mengembalikan nilai sebenarnya dalam metode next



:



class IterableObject extends Object {
    // same as before

    [Symbol.iterator]() {
        const entries = Object.entries(this);
        let index = 0;

        return {
            next() {
                return {
                    // we made a change here
                    value: entries[index],
                    done: false
                }
            }
        }
    }
}

      
      





Itu mudah. Kami hanya menggunakan variabel entries



dan index



untuk mengakses pasangan nilai kunci yang benar dari larik entries



.



Sekarang kita perlu berurusan dengan properti done



, karena sekarang akan selalu begitu false



. Anda dapat membuat satu variabel lagi selain entries



dan index



, dan memperbaruinya setelah setiap panggilan next



. Tapi ada cara yang lebih mudah. Mari kita periksa apakah index



arraynya di luar batas entries



:



class IterableObject extends Object {
    // same as before

    [Symbol.iterator]() {
        const entries = Object.entries(this);
        let index = 0;

        return {
            next() {
                return {
                    value: entries[index],
                    // we made a change here
                    done: index >= entries.length
                }
            }
        }
    }
}

      
      





Iterator kita berakhir ketika variabel index



sama atau lebih besar dari panjangnya entries



. Misalnya, jika y memiliki entries



panjang 3, maka itu berisi nilai-nilai pada indeks 0, 1 dan 2. Dan ketika variabel index



sama dengan atau lebih besar dari 3, itu berarti tidak ada nilai yang tersisa. Dilakukan.



Kode ini hampir berfungsi. Hanya ada satu hal yang perlu ditambahkan.



Variabel index



dimulai dari 0, tapi ... kami tidak memperbaruinya! Tidak sesederhana itu. Kita perlu memperbarui variabel setelah kita kembali { value, done }



. Tetapi ketika kami mengembalikannya, metodenya next



berhenti segera meskipun ada beberapa kode setelah ekspresi return



. Tapi kita bisa membuat objek { value, done }



, menyimpannya dalam variabel, memperbaruinya, index



dan baru kemudian mengembalikan objek:



class IterableObject extends Object {
    // same as before

    [Symbol.iterator]() {
        const entries = Object.entries(this);
        let index = 0;

        return {
            next() {
                const result = {
                    value: entries[index],
                    done: index >= entries.length
                };

                index++;

                return result;
            }
        }
    }
}

      
      





Setelah perubahan kami, kelasnya IterableObject



terlihat seperti ini:



class IterableObject extends Object {
    constructor(object) {
        super();
        Object.assign(this, object);
    }

    [Symbol.iterator]() {
        const entries = Object.entries(this);
        let index = 0;

        return {
            next() {
                const result = {
                    value: entries[index],
                    done: index >= entries.length
                };

                index++;

                return result;
            }
        }
    }
}

      
      





Kode berfungsi dengan baik, tetapi cukup membingungkan. Ini karena ini menunjukkan cara yang lebih cerdas tetapi kurang jelas untuk memperbarui index



setelah pembuatan objek result



. Kami hanya dapat menginisialisasi index



ke -1! Dan meskipun itu diperbarui sebelum objek kembali dari next



, semuanya akan berfungsi dengan baik, karena pembaruan pertama akan mengganti -1 dengan 0.



Jadi mari kita lakukan:



class IterableObject extends Object {
    // same as before

    [Symbol.iterator]() {
        const entries = Object.entries(this);
        let index = -1;

        return {
            next() {
                index++;

                return {
                    value: entries[index],
                    done: index >= entries.length
                }
            }
        }
    }
}

      
      





Seperti yang Anda lihat, sekarang kita tidak perlu mengatur urutan pembuatan result



dan pembaruan objek index



. Selama panggilan kedua, itu index



akan diperbarui menjadi 1, dan kami akan mengembalikan hasil yang berbeda, dan seterusnya. Semuanya berfungsi seperti yang kami inginkan, dan kodenya terlihat jauh lebih sederhana.



Tapi bagaimana kita memeriksa kebenaran pekerjaan itu? Anda dapat secara manual menjalankan metode [Symbol.iterator]()



untuk membuat instance iterator dan kemudian langsung memeriksa hasil panggilan next



. Tapi Anda bisa melakukan lebih mudah! Dikatakan di atas bahwa objek yang dapat diulang dapat dimasukkan ke dalam loop for ... of



. Mari kita lakukan itu, mencatat nilai yang dikembalikan oleh objek iterable kita di sepanjang jalan:



const iterableObject = new IterableObject({
    1: 'a',
    2: 'b',
    3: 'c'
});

for (let element of iterableObject) {
    console.log(element);
}

      
      





Bekerja! Inilah yang ditampilkan di konsol:



[ '1', 'a' ]
[ '2', 'b' ]
[ '3', 'c' ]

      
      





Keren! Kami mulai dengan objek yang tidak dapat digunakan dalam loop for ... of



, karena mereka tidak memiliki iterator bawaan. Tapi kami membuatnya sendiri IterableObject



, yang memiliki iterator yang ditulis sendiri.



Semoga sekarang Anda dapat melihat potensi iterable dan iterator. Ini adalah mekanisme yang memungkinkan Anda membuat struktur data Anda sendiri untuk bekerja dengan fungsi JS seperti loop for ... of



, dan berfungsi seperti struktur asli! Ini adalah fitur yang sangat berguna yang dapat sangat menyederhanakan kode Anda dalam situasi tertentu, terutama jika Anda berencana untuk sering mengulang struktur data.



Selain itu, kami dapat menyesuaikan apa sebenarnya yang harus dikembalikan oleh iterasi ini. Iterator kami sekarang mengembalikan pasangan nilai kunci. Bagaimana jika kita hanya menginginkan nilai? Mudah, cukup tulis ulang iteratornya:



class IterableObject extends Object {
    // same as before

    [Symbol.iterator]() {
        // changed `entries` to `values`
        const values = Object.values(this);
        let index = -1;

        return {
            next() {
                index++;

                return {
                    // changed `entries` to `values`
                    value: values[index],
                    // changed `entries` to `values`
                    done: index >= values.length
                }
            }
        }
    }
}

      
      





Dan itu dia! Jika sekarang kita memulai loop for ... of



, kita akan melihat di konsol:



a
b
c

      
      





Kami hanya mengembalikan nilai objek. Semua ini membuktikan fleksibilitas iterator yang ditulis sendiri. Anda dapat membuat mereka mengembalikan apa pun yang Anda inginkan.



Iterator sebagai ... objek yang dapat diulang



Sangat umum bagi orang untuk membingungkan iterator dan iterable. Ini adalah kesalahan dan saya telah mencoba memisahkan keduanya dengan rapi. Saya rasa saya tahu alasan mengapa orang begitu sering membingungkan mereka.



Ternyata iterator ... terkadang merupakan objek yang dapat diulang!



Apa artinya ini? Ingat, iterable adalah objek yang dikaitkan dengan iterator. Setiap iterator JavaScript asli memiliki metode [Symbol.iterator]()



yang mengembalikan iterator lain! Ini membuat iterator pertama menjadi objek yang bisa berulang.



Anda dapat memeriksa ini jika Anda mengambil iterator yang dikembalikan dari larik dan memanggilnya [Symbol.iterator]()



:



const ourArray = [1, 2, 3];

const iterator = ourArray[Symbol.iterator]();

const secondIterator = iterator[Symbol.iterator]();

console.log(secondIterator);

      
      





Setelah menjalankan kode ini, Anda akan melihat Object [Array Iterator] {}



. Artinya, sebuah iterator tidak hanya berisi iterator lain yang terkait dengannya, itu juga sebuah array.



Jika Anda membandingkan kedua iterator dengan menggunakan, ===,



ternyata keduanya sama persis:



const iterator = ourArray[Symbol.iterator]();

const secondIterator = iterator[Symbol.iterator]();

// logs `true`
console.log(iterator === secondIterator);

      
      





Pada awalnya, Anda mungkin merasa aneh dengan perilaku iterator yang merupakan iteratornya sendiri. Tetapi ini adalah fitur yang sangat berguna. Anda tidak dapat menempelkan iterator telanjang ke dalam satu loop for ... of



, itu hanya menerima objek yang dapat diulang - objek dengan metode [Symbol.iterator]()



.



Namun, situasi di mana iterator adalah iteratornya sendiri (dan karenanya merupakan objek yang dapat diulang) menyembunyikan masalah. Karena iterator JS asli berisi metode [Symbol.iterator]()



, Anda dapat meneruskannya langsung ke loop tanpa berpikir dua kali for ... of



.



Hasilnya, cuplikan ini:



const ourArray = [1, 2, 3];

for (let element of ourArray) {
    console.log(element);
}

      
      





dan yang satu ini:



const ourArray = [1, 2, 3];
const iterator = ourArray[Symbol.iterator]();

for (let element of iterator) {
    console.log(element);
}

      
      





bekerja dengan lancar dan melakukan hal yang sama. Tetapi mengapa ada orang yang menggunakan iterator seperti ini dalam loop secara langsung for ... of



? Terkadang itu tidak bisa dihindari.



Pertama, Anda mungkin perlu membuat iterator tanpa menjadi bagian dari iterable apa pun. Kami akan melihat contoh di bawah ini, dan ini tidak biasa. Terkadang kita tidak membutuhkan iterable itu sendiri.



Dan akan sangat canggung jika memiliki iterator kosong berarti Anda tidak dapat menggunakannya for ... of



. Tentu saja, Anda dapat melakukannya secara manual menggunakan metode next



dan, misalnya, loop while



, tetapi kami telah melihat bahwa untuk ini Anda harus menulis banyak kode, dan berulang.



Solusinya sederhana: jika Anda ingin menghindari kode boilerplate dan menggunakan iterator dalam satu loop for ... of



, Anda harus menjadikan iterator sebagai objek yang dapat berulang.



Di sisi lain, kami juga cukup sering mendapatkan iterator dari metode selain [Symbol.iterator]()



. Misalnya, ES6 Map



berisi metode entries



, values



dan keys



. Mereka semua mengembalikan iterator.



Jika iterator JS asli bukan juga objek yang dapat diulang, Anda tidak dapat menggunakan metode ini secara langsung dalam loop for ... of



, seperti ini:



for (let element of map.entries()) {
    console.log(element);
}

for (let element of map.values()) {
    console.log(element);
}

for (let element of map.keys()) {
    console.log(element);
}

      
      





Kode ini berfungsi karena iterator yang dikembalikan oleh metode juga merupakan objek yang dapat diulang. Jika tidak, Anda harus, katakanlah, membungkus hasil panggilan map.entries()



dalam beberapa objek iterable yang bodoh. Untungnya, kami tidak perlu melakukan ini.



Membuat objek iterable Anda sendiri dianggap sebagai praktik yang baik. Terutama jika dikembalikan dari metode selain [Symbol.iterator]()



. Sangat mudah untuk menjadikan iterator sebagai objek yang dapat berulang. Izinkan saya menunjukkan dengan contoh iterator IterableObject



:



class IterableObject extends Object {
    // same as before

    [Symbol.iterator]() {
        // same as before

        return {
            next() {
                // same as before
            },

            [Symbol.iterator]() {
                return this;
            }
        }
    }
}

      
      





Kami telah membuat metode di [Symbol.iterator]()



bawah metode tersebut next



. Menjadikan iterator ini sebagai iteratornya sendiri dengan hanya mengembalikan this



, yang berarti ia mengembalikan dirinya sendiri. Di atas kita telah melihat bagaimana sebuah array iterator berperilaku. Ini cukup agar iterator kita bekerja dalam loop for ... of



bahkan secara langsung.



Negara bagian Iterator



Sekarang sudah jelas bahwa setiap iterator memiliki status yang terkait dengannya. Misalnya, di iterator, IterableObject



kami menyimpan status - variabel index



- sebagai penutupan. Dan kami memperbaruinya setelah setiap langkah iterasi.



Apa yang terjadi setelah proses iterasi selesai? Iterator menjadi tidak berguna dan Anda dapat (harus!) Menghapusnya. Anda dapat melihat bahwa ini terjadi bahkan dengan contoh objek JS asli. Mari kita ambil iterator array dan coba jalankan dua kali dalam satu lingkaran for ... of



.



const ourArray = [1, 2, 3];

const iterator = ourArray[Symbol.iterator]();

for (let element of iterator) {
    console.log(element);
}

for (let element of iterator) {
    console.log(element);
}

      
      





Anda mungkin mengharapkan konsol untuk menampilkan nomor dua kali 1



, 2



dan 3



. Tapi hasilnya akan seperti ini:



1
2
3

      
      





Mengapa?



Mari kita panggil secara manual next



setelah loop berakhir:



const ourArray = [1, 2, 3];

const iterator = ourArray[Symbol.iterator]();

for (let element of iterator) {
    console.log(element);
}

console.log(iterator.next());

      
      





Log terakhir adalah keluaran ke konsol { value: undefined, done: true }



.



Itu dia. Setelah pengulangan selesai, iterator masuk ke status "selesai". Sekarang akan selalu mengembalikan objek { value: undefined, done: true }



.



Apakah ada cara untuk "mengatur ulang" status iterator agar dapat digunakan untuk kedua kalinya for ... of



? Dalam beberapa kasus, itu mungkin, tetapi tidak masuk akal. Oleh karena itu, ini [Symbol.iterator]



adalah metode, bukan hanya properti. Anda dapat memanggil metode ini lagi dan mendapatkan iterator lain :



const ourArray = [1, 2, 3];

const iterator = ourArray[Symbol.iterator]();

for (let element of iterator) {
    console.log(element);
}

const secondIterator = ourArray[Symbol.iterator]();

for (let element of secondIterator) {
    console.log(element);
}

      
      





Semuanya sekarang bekerja seperti yang diharapkan. Mari kita lihat mengapa beberapa perulangan maju melalui array berfungsi:



const ourArray = [1, 2, 3];

for (let element of ourArray) {
    console.log(element);
}

for (let element of ourArray) {
    console.log(element);
}

      
      





Semua loop for ... of



menggunakan iterator yang berbeda ! Setelah iterator dan loop selesai, iterator ini tidak lagi digunakan.



Iterator dan Array



Karena kita menggunakan iterator (walaupun secara tidak langsung) dalam loop for ... of



, iterator dapat terlihat seperti array. Tetapi ada dua perbedaan penting. Iterator dan Array menggunakan konsep greedy dan lazy values. Saat Anda membuat sebuah larik, pada saat tertentu ia memiliki panjang tertentu, dan nilainya sudah diinisialisasi. Tentu saja, Anda dapat membuat array tanpa nilai sama sekali, tetapi bukan itu masalahnya. Maksud saya adalah tidak mungkin membuat array yang menginisialisasi nilainya hanya setelah Anda mengaksesnya dengan menulis array[someIndex]



. Mungkin mungkin untuk menyiasati ini dengan proxy atau trik lain, tetapi secara default array JavaScript tidak berperilaku seperti itu.



Dan ketika mereka mengatakan bahwa sebuah array memiliki panjang, itu berarti panjangnya terbatas. Tidak ada larik tak terbatas dalam JavaScript.



Kedua kualitas ini menunjukkan keserakahan dari array.



Dan iterator malas .



Untuk mendemonstrasikan ini, kita akan membuat dua dari iterator kita: yang pertama tidak terbatas, tidak seperti array terbatas, dan yang kedua akan menginisialisasi nilainya hanya ketika diminta oleh pengguna iterator.



Mari kita mulai dengan iterator tak terbatas. Kedengarannya mengintimidasi, tetapi sangat mudah dibuat: iterator dimulai dari 0 dan mengembalikan angka berikutnya dalam urutan di setiap langkah. Selama-lamanya.



const counterIterator = {
    integer: -1,

    next() {
        this.integer++;
        return { value: this.integer, done: false };
    },

    [Symbol.iterator]() {
        return this;
    }
}

      
      





Dan itu dia! Kami mulai dengan properti integer



-1. Setiap kali next



kami menyebutnya, kami menambahnya dengan 1 dan mengembalikannya sebagai objek value



. Perhatikan bahwa kita menggunakan trik yang disebutkan di atas lagi: kita mulai pada -1 untuk mengembalikan 0 untuk pertama kalinya.



Juga lihat propertinya done



. Itu akan selalu salah. Iterator ini tidak berakhir!



Selain itu, kami membuat iterator menjadi iterable dengan memberikan implementasi sederhana [Symbol.iterator]()



.



Satu hal terakhir: ini adalah kasus yang saya sebutkan di atas - kami membuat iterator, tetapi tidak membutuhkan orang tua yang dapat berulang untuk berfungsi.



Sekarang mari kita coba iterator ini dalam satu lingkaran for ... of



. Anda hanya perlu mengingat untuk menghentikan loop di beberapa titik, jika tidak kode akan dieksekusi selamanya.



for (let element of counterIterator) {
    if (element > 5) {
        break;
    }
    
    console.log(element);
}

      
      





Setelah diluncurkan, kita akan melihat di konsol:



0
1
2
3
4
5

      
      





Kami sebenarnya telah membuat iterator tak terbatas yang mengembalikan angka sebanyak yang Anda suka. Dan sangat mudah membuatnya!



Sekarang mari kita tulis iterator yang tidak membuat nilai sampai mereka diminta.



Nah ... kita sudah melakukannya!



Pernahkah Anda memperhatikan bahwa ia counterIterator



hanya menyimpan satu nomor properti pada satu waktu integer



? Ini adalah nomor terakhir yang dibalas di telepon next



. Dan ini adalah kemalasan yang sama. Sebuah iterator berpotensi mengembalikan angka apa pun (lebih tepatnya, bilangan bulat positif). Tapi itu membuatnya hanya ketika dibutuhkan: ketika metode dipanggil next



.



Ini mungkin terlihat sangat menarik perhatian. Bagaimanapun, angka dibuat dengan cepat dan tidak memakan banyak ruang memori. Tetapi jika Anda bekerja dengan objek yang sangat besar yang memakan banyak memori, terkadang mengganti array dengan iterator bisa sangat berguna, mempercepat program dan menghemat memori.



Semakin besar objeknya (atau semakin lama waktu yang dibutuhkan untuk membuatnya), semakin besar manfaatnya.



Cara Lain Menggunakan Iterator



Sejauh ini, kami hanya menggunakan iterator dalam satu loop for ... of



atau secara manual menggunakan next



. Tapi ini bukan satu-satunya cara.



Kita telah melihat bahwa konstruktor Map



menganggap iterabel sebagai argumen. Anda juga dapat Array.from



dengan mudah mengubah sebuah iterable menjadi array menggunakan metode ini . Tetapi berhati-hatilah! Seperti yang saya katakan, kemalasan dari iterator terkadang bisa menjadi keuntungan besar. Mengonversi ke larik menghilangkan kemalasan. Semua nilai yang dikembalikan oleh iterator segera diinisialisasi dan kemudian ditempatkan ke dalam array. Artinya jika kita mencoba mengubah infinite counterIterator



menjadi array, itu akan mengakibatkan bencana. Array.from



akan dieksekusi selamanya tanpa mengembalikan hasil. Jadi sebelum mengonversi iterable / iterator menjadi array, Anda perlu memastikan operasinya aman.



Menariknya, iterable juga bekerja dengan baik dengan operator penyebaran (...



.) Ingatlah bahwa ini bekerja dengan Array.from



cara yang sama ketika semua nilai iterator dibuat sekaligus. Misalnya, Anda dapat membuat versi Anda sendiri menggunakan operator penyebaran Array.from



. Cukup terapkan operator ke iterable, lalu masukkan nilainya ke dalam array:



const arrayFromIterator = [...iterable];

      
      





Anda juga bisa mendapatkan semua nilai dari iterable dan menerapkannya ke fungsi:



someFunction(...iterable);

      
      





Kesimpulan



Saya harap Anda sekarang memahami judul artikel Objek dan Iterator yang Dapat Diulang. Kami mempelajari apa itu, bagaimana perbedaannya, bagaimana menggunakannya, dan bagaimana membuat contoh kita sendiri. Kami sekarang sepenuhnya siap untuk bekerja dengan generator. Jika Anda sudah terbiasa dengan iterator, beralih ke topik berikutnya seharusnya tidak terlalu sulit.



All Articles