Итерируемые объекты и протокол итерации #
Итерируемый объект (iterable) предоставляет метод [Symbol.iterator](), возвращающий итератор — объект с методом next(), который выдаёт значения { value, done }. Протокол итерации лежит в основе for...of, spread и части API коллекций; понимание его нужно до генераторов и async-итераторов.
Итератор вручную #
Минимальный итератор для трёх кодов статуса доставки:
function createStatusIterator(statuses) {
let index = 0
return {
next() {
if (index < statuses.length) {
return { value: statuses[index++], done: false }
}
return { value: undefined, done: true }
},
}
}
const it = createStatusIterator(['pending', 'shipped', 'delivered'])
it.next() // { value: 'pending', done: false }
it.next() // { value: 'shipped', done: false }
it.next() // { value: 'delivered', done: false }
it.next() // { value: undefined, done: true }
Когда done: true, value обычно undefined и дальнейшие вызовы next() не обязаны менять состояние.
Итерируемый объект и Symbol.iterator
#
Чтобы объект работал с for...of, он должен быть iterable:
const deliverySteps = {
steps: ['сборка', 'передача курьеру', 'в пути', 'вручение'],
[Symbol.iterator]() {
let i = 0
const steps = this.steps
return {
next() {
return i < steps.length
? { value: steps[i++], done: false }
: { done: true, value: undefined }
},
}
},
}
for (const step of deliverySteps) {
console.log(step)
}
Symbol.iterator — well-known symbol; движок ищет именно это свойство (см. Symbol и перечисление ключей).
for...of
#
Цикл вызывает iterable[Symbol.iterator]() и повторяет next() до done: true:
const cartSkus = ['KB-01', 'MS-03', 'HD-07']
for (const sku of cartSkus) {
console.log(sku)
}
Работает с любым встроенным iterable:
| Тип | Что перебирается |
|---|---|
Array | элементы |
String | символы (UTF-16 code units) |
Map | пары [key, value] |
Set | значения |
arguments | аргументы функции |
const tariffs = new Map([
['standard', 300],
['express', 590],
])
for (const [name, price] of tariffs) {
console.log(`${name}: ${price} ₽`)
}
for...of не перебирает перечисляемые свойства объекта — для plain object используйте Object.keys или Object.entries (см. Symbol и перечисление ключей).
Отличие от for...in
#
for...in обходит перечисляемые строковые ключи объекта и унаследованные из цепочки прототипов — для массивов это часто нежелательно:
const playlist = ['track-a', 'track-b']
playlist.customTag = 'focus' // не делайте так в проде
for (const key in playlist) {
console.log(key) // "0", "1", "customTag"
}
for (const track of playlist) {
console.log(track) // "track-a", "track-b"
}
Для коллекций с индексами предпочитайте for...of или методы массива.
Spread и деструктуризация #
Spread на iterable создаёт массив (или копию):
const queue = new Set(['ord-1', 'ord-2', 'ord-3'])
const list = [...queue] // ["ord-1", "ord-2", "ord-3"]
Деструктуризация с rest тоже использует итератор:
const [first, second, ...rest] = 'checkout'
// first "c", second "h", rest ["e","c","k","o","u","t"]
Строка как iterable #
for (const ch of 'доставка') {
// по одному символу (суррогатные пары — с осторожностью; для emoji — Array.from или [...str] с Unicode)
}
Длина строки и итерация по символам — в Строки.
Практика: обход корзины и лимит #
function* cartLines(items) {
for (const item of items) {
yield `${item.sku} × ${item.qty}`
}
}
const cart = [
{ sku: 'KB-01', qty: 1 },
{ sku: 'MS-03', qty: 2 },
]
[...cartLines(cart)]
// ["KB-01 × 1", "MS-03 × 2"]
Здесь уже используется генератор — подробнее в Генераторы. Пока достаточно знать: генератор возвращает iterable с готовым Symbol.iterator.
function take(iterable, limit) {
const iterator = iterable[Symbol.iterator]()
const result = []
for (let n = 0; n < limit; n++) {
const { value, done } = iterator.next()
if (done) break
result.push(value)
}
return result
}
take(['t1', 't2', 't3', 't4'], 2) // ["t1", "t2"]
Итог #
- Итератор реализует
next()→{ value, done }; iterable — метод[Symbol.iterator](). for...of, spread и rest-деструктуризация работают только с iterable, не с произвольными объектами.- Массивы, строки,
Map,Set— iterable из коробки; plain object — нет. for...in— ключи объекта;for...of— значения из протокола итерации.