Learning Book

Протокол итерации

Протокол итерации

for...of, spread-оператор и деструктуризация работают с любым объектом, реализующим протокол итерации. Понять этот протокол — значит понять, как JS обходит коллекции.

Итерируемый объект и итератор

Итерируемый объект (iterable) — объект с методом Symbol.iterator, возвращающим итератор.

Итератор — объект с методом next(), возвращающим { value, done }.

// Вручную реализуем итерируемый диапазон
const range = {
  from: 1,
  to: 3,
  [Symbol.iterator]() {
    let current = this.from
    const last = this.to
    return {
      next() {
        if (current <= last) {
          return { value: current++, done: false }
        }
        return { value: undefined, done: true }
      }
    }
  }
}

for (const n of range) {
  console.log(n) // 1, 2, 3
}

console.log([...range])     // [1, 2, 3]
const [a, b] = range        // деструктуризация
Встроенные итерируемые: массивы, строки, Map, Set, NodeList, аргументы функции. Обычные объекты {} — НЕ итерируемые.

Как работает for…of

for...of — синтаксический сахар над протоколом итерации:

// for...of раскрывается примерно в:
const iter = range[Symbol.iterator]()
let result = iter.next()
while (!result.done) {
  const n = result.value
  console.log(n)
  result = iter.next()
}

Бесконечные последовательности

Итератор может никогда не возвращать done: true — это бесконечная последовательность:

// Бесконечный счётчик
function counter(start = 0) {
  return {
    [Symbol.iterator]() {
      let i = start
      return {
        next() { return { value: i++, done: false } }
      }
    }
  }
}

// Берём только первые 5 чисел через деструктуризацию
// ОСТОРОЖНО: [...counter()] зависнет навсегда!
// Используй break или take()
for (const n of counter(10)) {
  if (n > 14) break
  console.log(n) // 10, 11, 12, 13, 14
}
Протокол синхронной итерации использует Symbol.iterator. Для асинхронной — Symbol.asyncIterator, который поддерживает for await...of. Async-итераторы возвращают Promise<{ value, done }> из .next().