Objek yang dapat diubah adalah generalisasi dari array. Itu adalah konsep yang memungkinkan kita membuat objek apa pun dapat digunakan dalam perulangan for..of
.
Tentu saja, Array dapat diubah. Namun ada banyak objek bawaan lainnya yang juga dapat diubah. Misalnya, string juga dapat diubah.
Jika suatu objek secara teknis bukan array, tetapi mewakili kumpulan (daftar, kumpulan) sesuatu, maka for..of
adalah sintaks yang bagus untuk mengulangnya, jadi mari kita lihat cara membuatnya berfungsi.
Kita dapat dengan mudah memahami konsep iterable dengan membuatnya sendiri.
Misalnya, kita mempunyai objek yang bukan array, namun terlihat cocok untuk for..of
.
Seperti objek range
yang mewakili interval angka:
misalkan rentang = { dari: 1, untuk: 5 }; // Kita ingin for..of berfungsi: // untuk(biarkan jumlah rentang) ... angka=1,2,3,4,5
Untuk membuat objek range
dapat diubah (dan membiarkan for..of
berfungsi) kita perlu menambahkan metode ke objek bernama Symbol.iterator
(simbol bawaan khusus hanya untuk itu).
Ketika for..of
dimulai, ia memanggil metode itu satu kali (atau kesalahan jika tidak ditemukan). Metode harus mengembalikan iterator – objek dengan metode next
.
Selanjutnya, for..of
hanya berfungsi dengan objek yang dikembalikan itu .
Ketika for..of
menginginkan nilai berikutnya, ia memanggil next()
pada objek itu.
Hasil next()
harus berbentuk {done: Boolean, value: any}
, di mana done=true
berarti perulangan telah selesai, jika tidak, value
adalah nilai berikutnya.
Berikut implementasi lengkap untuk range
dengan keterangan:
misalkan rentang = { dari: 1, untuk: 5 }; // 1. panggilan ke for..of awalnya memanggil ini rentang[Simbol.iterator] = fungsi() { // ...itu mengembalikan objek iterator: // 2. Selanjutnya, for..of hanya bekerja dengan objek iterator di bawah, menanyakan nilai selanjutnya kembali { saat ini: ini.dari, terakhir: ini.ke, // 3. next() dipanggil pada setiap iterasi oleh perulangan for..of Berikutnya() { // 4. harus mengembalikan nilai sebagai objek {selesai:.., nilai :...} if (ini.saat ini <= ini.terakhir) { return { selesai: salah, nilai: ini.saat ini++ }; } kalau tidak { kembali { selesai: benar }; } } }; }; // sekarang berhasil! for (biarkan jumlah rentang) { peringatan(angka); // 1, lalu 2, 3, 4, 5 }
Harap perhatikan fitur inti dari iterable: pemisahan perhatian.
range
itu sendiri tidak memiliki metode next()
.
Sebaliknya, objek lain, yang disebut “iterator” dibuat dengan panggilan ke range[Symbol.iterator]()
, dan next()
menghasilkan nilai untuk iterasi.
Jadi, objek iterator terpisah dari objek yang diiterasinya.
Secara teknis, kami dapat menggabungkannya dan menggunakan range
itu sendiri sebagai iterator untuk membuat kode lebih sederhana.
Seperti ini:
misalkan rentang = { dari: 1, ke: 5, [Simbol.iterator]() { ini.saat ini = ini.dari; kembalikan ini; }, Berikutnya() { if (ini.saat ini <= ini.ke) { return { selesai: salah, nilai: ini.saat ini++ }; } kalau tidak { kembali { selesai: benar }; } } }; for (biarkan jumlah rentang) { peringatan(angka); // 1, lalu 2, 3, 4, 5 }
Sekarang range[Symbol.iterator]()
mengembalikan objek range
itu sendiri: ia memiliki metode next()
yang diperlukan dan mengingat kemajuan iterasi saat ini di this.current
. Singkat? Ya. Dan terkadang itu juga tidak masalah.
Kelemahannya adalah sekarang tidak mungkin untuk menjalankan dua for..of
loop pada objek secara bersamaan: keduanya akan berbagi status iterasi, karena hanya ada satu iterator – objek itu sendiri. Namun dua for-of paralel adalah hal yang jarang terjadi, bahkan dalam skenario asinkron.
Iterator tak terbatas
Iterator tak terbatas juga dimungkinkan. Misalnya, range
menjadi tak terbatas untuk range.to = Infinity
. Atau kita bisa membuat objek iterable yang menghasilkan barisan bilangan pseudorandom tak terhingga. Juga dapat bermanfaat.
Tidak ada batasan pada next
, ia dapat mengembalikan nilai lebih banyak, itu normal.
Tentu saja, perulangan for..of
pada iterable seperti itu tidak akan ada habisnya. Tapi kita selalu bisa menghentikannya menggunakan break
.
Array dan string adalah iterable bawaan yang paling banyak digunakan.
Untuk sebuah string, for..of
mengulang karakternya:
for (biarkan char dari "test") { // terpicu 4 kali: satu kali untuk setiap karakter peringatan( karakter ); // t, lalu e, lalu s, lalu t }
Dan itu berfungsi dengan benar dengan pasangan pengganti!
biarkan str = '??'; untuk (biarkan char dari str) { peringatan( karakter ); // ?, kemudian ? }
Untuk pemahaman lebih dalam, mari kita lihat cara menggunakan iterator secara eksplisit.
Kami akan mengulangi string dengan cara yang persis sama seperti for..of
, tetapi dengan panggilan langsung. Kode ini membuat iterator string dan mendapatkan nilai darinya “secara manual”:
biarkan str = "Halo"; // melakukan hal yang sama seperti // untuk (biarkan char dari str) alert(char); biarkan iterator = str[Simbol.iterator](); sementara (benar) { biarkan hasil = iterator.next(); if (hasil.selesai) rusak; alert(hasil.nilai); // mengeluarkan karakter satu per satu }
Hal ini jarang diperlukan, namun memberi kita kendali lebih besar terhadap proses dibandingkan for..of
. Misalnya, kita dapat membagi proses iterasi: iterasi sedikit, lalu berhenti, lakukan hal lain, lalu lanjutkan lagi nanti.
Dua istilah resmi terlihat serupa, namun sangat berbeda. Harap pastikan Anda memahaminya dengan baik untuk menghindari kebingungan.
Iterables adalah objek yang mengimplementasikan metode Symbol.iterator
, seperti dijelaskan di atas.
Seperti array adalah objek yang memiliki indeks dan length
, sehingga terlihat seperti array.
Saat kita menggunakan JavaScript untuk tugas praktis di browser atau lingkungan lainnya, kita mungkin menemukan objek yang dapat diubah atau mirip array, atau keduanya.
Misalnya, string dapat diubah ( for..of
berfungsi pada string tersebut) dan seperti array (memiliki indeks numerik dan length
).
Tapi sebuah iterable mungkin tidak seperti array. Dan sebaliknya, bentuk seperti array mungkin tidak dapat diubah.
Misalnya, range
pada contoh di atas dapat diubah, namun tidak seperti array, karena tidak memiliki properti terindeks dan length
.
Dan inilah objek yang berbentuk array, namun tidak dapat diubah:
biarkan arrayLike = { // memiliki indeks dan panjang => seperti array 0: "Halo", 1: "Dunia", panjang: 2 }; // Kesalahan (tidak ada Simbol.iterator) untuk (biarkan item arrayLike) {}
Iterable dan array-likes biasanya bukan arrays , mereka tidak memiliki push
, pop
dll. Itu agak merepotkan jika kita memiliki objek seperti itu dan ingin bekerja dengannya sebagai array. Misalnya kita ingin bekerja dengan range
menggunakan metode array. Bagaimana cara mencapainya?
Ada metode universal Array.from yang mengambil nilai yang dapat diubah atau mirip array dan membuat Array
"nyata" darinya. Lalu kita bisa memanggil metode array di dalamnya.
Misalnya:
biarkan arraySuka = { 0: "Halo", 1: "Dunia", panjang: 2 }; biarkan arr = Array.from(arrayLike); // (*) peringatan(arr.pop()); // Dunia (metode berhasil)
Array.from
pada baris (*)
mengambil objek, memeriksanya apakah dapat diubah atau mirip array, lalu membuat array baru dan menyalin semua item ke dalamnya.
Hal yang sama terjadi pada iterable:
// dengan asumsi rentang tersebut diambil dari contoh di atas let arr = Array.from(range); peringatan(arr); // 1,2,3,4,5 (konversi array keString berfungsi)
Sintaks lengkap untuk Array.from
juga memungkinkan kita menyediakan fungsi “pemetaan” opsional:
Array.dari(obj[, mapFn, thisArg])
Argumen opsional kedua mapFn
bisa menjadi fungsi yang akan diterapkan ke setiap elemen sebelum menambahkannya ke array, dan thisArg
memungkinkan kita this
untuk elemen tersebut.
Misalnya:
// dengan asumsi rentang tersebut diambil dari contoh di atas // kuadratkan setiap angka let arr = Array.from(range, num => num * num); peringatan(arr); // 1,4,9,16,25
Di sini kita menggunakan Array.from
untuk mengubah string menjadi array karakter:
biarkan str = '??'; // membagi str menjadi beberapa karakter biarkan karakter = Array.from(str); peringatan(karakter[0]); // ? peringatan(karakter[1]); // ? alert(karakter.panjang); // 2
Tidak seperti str.split
, ini bergantung pada sifat string yang dapat diubah sehingga, seperti for..of
, berfungsi dengan benar dengan pasangan pengganti.
Secara teknis di sini fungsinya sama dengan:
biarkan str = '??'; biarkan karakter = []; // Array.from secara internal melakukan perulangan yang sama untuk (biarkan char dari str) { karakter.push(char); } peringatan(karakter);
…Tapi ini lebih pendek.
Kita bahkan dapat membuat slice
sadar pengganti di atasnya:
potongan fungsi(str, awal, akhir) { return Array.from(str).slice(mulai, akhir).join(''); } biarkan str = '???'; waspada( irisan(str, 1, 3) ); // ?? // metode asli tidak mendukung pasangan pengganti waspada( str.slice(1, 3) ); // sampah (dua bagian dari pasangan pengganti yang berbeda)
Objek yang dapat digunakan di for..of
disebut iterable .
Secara teknis, iterable harus mengimplementasikan metode bernama Symbol.iterator
.
Hasil dari obj[Symbol.iterator]()
disebut iterator . Ini menangani proses iterasi lebih lanjut.
Sebuah iterator harus memiliki metode bernama next()
yang mengembalikan objek {done: Boolean, value: any}
, di sini done:true
menunjukkan akhir dari proses iterasi, jika tidak, value
adalah nilai berikutnya.
Metode Symbol.iterator
dipanggil secara otomatis oleh for..of
, tapi kita juga bisa melakukannya secara langsung.
Iterable bawaan seperti string atau array, juga mengimplementasikan Symbol.iterator
.
String iterator mengetahui tentang pasangan pengganti.
Objek yang memiliki properti dan length
yang diindeks disebut seperti array . Objek tersebut mungkin juga memiliki properti dan metode lain, namun tidak memiliki metode array bawaan.
Jika kita melihat ke dalam spesifikasi – kita akan melihat bahwa sebagian besar metode bawaan berasumsi bahwa metode tersebut bekerja dengan iterable atau mirip array, bukan array “nyata”, karena itu lebih abstrak.
Array.from(obj[, mapFn, thisArg])
membuat Array
nyata dari obj
yang dapat diubah atau mirip array, dan kita kemudian dapat menggunakan metode array di dalamnya. Argumen opsional mapFn
dan thisArg
memungkinkan kita menerapkan fungsi ke setiap item.