Итерируемые объекты и протокол итерации

Итерируемые объекты и протокол итерации #

Итерируемый объект (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 — значения из протокола итерации.

См. также #