W3docs

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

Итерируемые объекты — ключевая концепция JavaScript. Изучите протокол итератора, встроенные итерируемые типы и практические приёмы работы с коллекциями.

Введение в итерируемые объекты JavaScript

Итерируемые объекты JavaScript — это объекты, реализующие специальный протокол, позволяющий использовать их в конструкциях итерации, таких как for...of. В этом руководстве рассматриваются ключевые концепции, встроенные итерируемые типы и практические приёмы работы с коллекциями.

Что такое итерируемые объекты в JavaScript?

По своей сути итерируемый объект — это объект, реализующий метод Symbol.iterator, обеспечивающий последовательный доступ к его элементам. Ряд встроенных типов в JavaScript является итерируемым, включая Array, String, Map, Set и другие. Важно различать итерируемый объект (объект, по которому выполняется обход) и итератор (объект, возвращаемый методом Symbol.iterator, который фактически выполняет обход). Итерируемые объекты являются неотъемлемой частью различных операций — цикличного обхода и манипуляций с данными.

Информация

Подробное описание объектов JavaScript Map и Set приведено в нашем полном руководстве JavaScript Map and Set.

Пример итерируемого объекта: Array

Рассмотрим простой пример итерируемого объекта в JavaScript:

javascript— editable

Этот фрагмент кода демонстрирует итерацию по array фруктов — типичному итерируемому объекту. Цикл for...of автоматически вызывает метод Symbol.iterator итерируемого объекта и потребляет полученный итератор до тех пор, пока done не станет равным true. Полный обзор семейства циклов см. в разделе Циклы JavaScript.

Внимание

Не путайте for...of и for...in. for...of перебирает значения, производимые итерируемым объектом (элементы array, символы string, записи Map). for...in перебирает перечисляемые ключи свойств объекта — включая унаследованные — и предназначен для обычных объектов, а не array. Применение for...in к array даёт строковые индексы ("0", "1", …) и может захватить лишние свойства, поэтому для упорядоченных данных предпочтительнее использовать for...of.

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

Основой итерируемого объекта служит его метод Symbol.iterator. Протокол — это точный контракт:

  1. Итерируемый объект имеет метод с ключом Symbol.iterator. При вызове он возвращает итератор.
  2. Итератор — это объект с методом next().
  3. Каждый вызов next() возвращает объект { value, done }:
    • done: falsevalue является следующим элементом последовательности.
    • done: true — итерация завершена (value в этом случае игнорируется или содержит необязательный финальный результат).

Любой объект, следующий этому контракту, работает с for...of, оператором spread, деструктурированием и Array.from() — даже если вы написали объект самостоятельно. Symbol — это встроенный уникальный ключ; о том, почему методы протокола используют его вместо обычного строкового имени, см. в разделе Тип Symbol.

Пример: пользовательский итерируемый объект range

Классический вариант использования — объект, лениво генерирующий числовой диапазон без построения array:

javascript— editable

В этом примере метод [Symbol.iterator]() каждый раз возвращает свежий итератор, поэтому один и тот же объект range можно обходить более одного раза. Сокращённый синтаксис метода гарантирует, что this корректно ссылается на объект range. (Использование стрелочной функции для [Symbol.iterator] привязало бы this лексически и нарушило бы паттерн.)

Генераторы: простой способ создания итерируемых объектов

Написание next() и ручное отслеживание состояния — громоздко. Функция-генератор — объявленная с помощью function* и использующая yield — автоматически создаёт итератор. Каждый yield приостанавливает функцию и передаёт значение потребителю; выполнение возобновляется при следующем вызове next(). Тот же объект range становится значительно короче:

javascript— editable

Символ * перед именем метода делает его методом-генератором, поэтому range становится итерируемым практически без шаблонного кода. Полное описание возможностей генераторов — включая двустороннюю коммуникацию и делегирование с помощью yield* — см. в разделах Генераторы и Асинхронные итераторы и генераторы.

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

Поскольку итератор вычисляет следующее значение только по запросу, он может описывать последовательности, которые слишком велики — или даже бесконечны — чтобы хранить их в памяти. Именно по этой причине стоит писать пользовательский итератор вместо обычного array:

javascript— editable
Внимание

Никогда не используйте spread ([...naturals()]) или for...of без break с бесконечным итерируемым объектом — это приведёт к бесконечному циклу. Вместо этого получайте конечное число значений с помощью next().

Потребление итерируемых объектов

Как только объект становится итерируемым, весь язык открывается для него: любая конструкция, принимающая итерируемый объект, работает с вашим пользовательским типом так же, как с array.

Использование Array.from()

Метод Array.from() создаёт новый array из любого итерируемого (или массивоподобного) объекта. Он также принимает необязательную функцию отображения в качестве второго аргумента, применяемую к каждому элементу при построении array — это удобнее, чем Array.from(it).map(fn), поскольку позволяет избежать второго прохода:

javascript— editable

Подробнее о Set см. в разделе JavaScript Map and Set, а о доступных операциях с array — в разделе Методы array.

Синтаксис spread с итерируемыми объектами

Синтаксис spread (...) разворачивает итерируемый объект там, где ожидаются аргументы или элементы — для объединения array, копирования или передачи элементов в качестве аргументов функции:

javascript— editable

Полное описание применения ... как в позиции spread, так и в позиции сбора значений см. в разделе Остаточные параметры и синтаксис spread.

Деструктурирование и остаточные элементы

Деструктурирующее присваивание извлекает значения из любого итерируемого объекта по позиции, а паттерн rest (...) собирает оставшиеся значения в array:

javascript— editable

Подробнее см. в разделе Деструктурирующее присваивание.

Итерируемые строки и безопасная работа с Unicode

Строки итерируемы, и, что важно, итератор обходит кодовые точки Unicode, а не 16-битные единицы кода. Это означает, что символы суррогатных пар (эмодзи, некоторые письменности) сохраняются целиком — в отличие от индексирования через [i] или старых циклов for по .length, которые могут их разбивать:

javascript— editable

Когда нужно корректно подсчитать или разбить символы, видимые пользователю, итерируйте строку (или разворачивайте её через spread) вместо того, чтобы опираться на .length. Подробнее см. в главе Строки.

Итоги

  • Объект является итерируемым, если он имеет метод [Symbol.iterator](), возвращающий итератор — объект, чей next() выдаёт { value, done }.
  • Используйте генератор (function* / yield) вместо написания next() вручную — это самый краткий и надёжный способ сделать что-либо итерируемым.
  • Используйте for...of для значений упорядоченных/итерируемых данных; for...in — только для ключей обычных объектов.
  • Став итерируемым, ваш тип бесплатно получает поддержку spread, деструктурирования, rest, Array.from(it, mapFn) и многих встроенных API.
  • Итераторы ленивы, поэтому они могут моделировать бесконечные или очень большие последовательности, которые array никогда бы не смог вместить.
  • Итерируйте строки (не индексируйте их), чтобы безопасно обрабатывать символы Unicode, такие как эмодзи.

Практика

Практика
Какие из следующих утверждений об итерируемых объектах JavaScript верны?
Какие из следующих утверждений об итерируемых объектах JavaScript верны?
Was this page helpful?