что значит итерируемый объект
Перебираемые объекты
Конечно же, сами массивы являются перебираемыми объектами. Но есть и много других встроенных перебираемых объектов, например, строки.
Если объект не является массивом, но представляет собой коллекцию каких-то элементов, то удобно использовать цикл for..of для их перебора, так что давайте посмотрим, как это сделать.
Symbol.iterator
Мы легко поймём принцип устройства перебираемых объектов, создав один из них.
Вот полная реализация range с пояснениями:
Обратите внимание на ключевую особенность итераторов: разделение ответственности.
Таким образом, итератор отделён от самого итерируемого объекта.
Технически мы можем объединить их и использовать сам range как итератор, чтобы упростить код.
Метод next не имеет ограничений, он может возвращать всё новые и новые значения, это нормально.
Строка – перебираемый объект
Среди встроенных перебираемых объектов наиболее широко используются массивы и строки.
Для строки for..of перебирает символы:
И он работает корректно даже с суррогатными парами!
Явный вызов итератора
Чтобы понять устройство итераторов чуть глубже, давайте посмотрим, как их использовать явно.
Итерируемые объекты и псевдомассивы
Есть два официальных термина, которые очень похожи, но в то же время сильно различаются. Поэтому убедитесь, что вы как следует поняли их, чтобы избежать путаницы.
При использовании JavaScript в браузере или других окружениях мы можем встретить объекты, которые являются итерируемыми или псевдомассивами, или и тем, и другим.
Например, строки итерируемы (для них работает for..of ) и являются псевдомассивами (они индексированы и есть length ).
Но итерируемый объект может не быть псевдомассивом. И наоборот: псевдомассив может не быть итерируемым.
А вот объект, который является псевдомассивом, но его нельзя итерировать:
Array.from
Array.from в строке (*) принимает объект, проверяет, является ли он итерируемым объектом или псевдомассивом, затем создаёт новый массив и копирует туда все элементы.
То же самое происходит с итерируемым объектом:
Полный синтаксис Array.from позволяет указать необязательную «трансформирующую» функцию:
Необязательный второй аргумент может быть функцией, которая будет применена к каждому элементу перед добавлением в массив, а thisArg позволяет установить this для этой функции.
Технически это то же самое, что и:
Итого
Если мы заглянем в спецификацию, мы увидим, что большинство встроенных методов рассчитывают на то, что они будут работать с итерируемыми объектами или псевдомассивами вместо «настоящих» массивов, потому что эти объекты более абстрактны.
Итерируемый объект, итератор и генератор
Привет, уважаемые читатели Хабрахабра. В этой статье попробуем разобраться что такое итерируемый объект, итератор и генератор. Рассмотрим как они реализованы и используются. Примеры написан на Python, но итераторы и генераторы, на мой взгляд, фундаментальные понятия, которые были актуальны 20 лет назад и еще более актуальны сейчас, при этом за это время фактически не изменились.
Итераторы
Для начала вспомним, что из себя представляет паттерн «Итератор(Iterator)».
Назначение:
Существуют два вида итераторов, внешний и внутренний.
Внешний итератор — это классический (pull-based) итератор, когда процессом обхода явно управляет клиент путем вызова метода Next.
Внутренний итератор — это push-based-итератор, которому передается callback функция, и он сам уведомляет клиента о получении следующего элемента.
Классическая диаграмма паттерна “Итератор”, как она описана в небезызвестной книги «банды четырех»:
Aggregate — составной объект, по которому может перемещаться итератор;
Iterator — определяет интерфейс итератора;
ConcreteAggregate — конкретная реализация агрегата;
ConcreteIterator — конкретная реализация итератора для определенного агрегата;
Client — использует объект Aggregate и итератор для его обхода.
Пробуем реализовать на Python классический итератор
Конкретная реализация итератора для списка:
Конкретная реализация агрегата:
Теперь мы можем создать объект коллекции и обойти все ее элементы с помощью итератора:
А так как мы реализовали метод first, который сбрасывает итератор в начальное состояние, то можно воспользоваться этим же итератором еще раз:
Реализации могут быть разные, но основная идея в том, что итератор может обходить различные структуры, вектора, деревья, хеш-таблицы и много другое, при этом имея снаружи одинаковый интерфейс.
Протокол итерирования в Python
В книге «банды четырех» о реализации итератора написано:
Минимальный интерфейс класса Iterator состоит из операций First, Next, IsDone и CurrentItem. Но если очень хочется, то этот интерфейс можно упростить, объединив операции Next, IsDone и CurrentItem в одну, которая будет переходить к следующему объекту и возвращать его. Если обход завершен, то эта операция вернет специальное значения(например, 0), обозначающее конец итерации.
Именно так и реализовано в Python, но вместо специального значения, о конце итерации говорит StopIteration. Проще просить прощения, чем разрешения.
Сначала важно определиться с терминами.
Рассмотрим итерируемый объект (Iterable). В стандартной библиотеке он объявлен как абстрактный класс collections.abc.Iterable:
У него есть абстрактный метод __iter__ который должен вернуть объект итератора. И метод __subclasshook__ который проверяет наличие у класса метод __iter__. Таким образом, получается, что итерируемый объект это любой объект который реализует метод __iter__
Но есть один момент, это функция iter(). Именно эту функцией использует например цикл for для получения итератора. Функция iter() в первую очередь для получения итератора из объекта, вызывает его метод __iter__. Если метод не реализован, то она проверяет наличие метода __getitem__ и если он реализован, то на его основе создается итератор. __getitem__ должен принимать индекс с нуля. Если не реализован ни один из этих методов, тогда будет вызвано исключение TypeError.
Итого, итерируемый объект — это любой объект, от которого встроенная функция iter() может получить итератор. Последовательности(abc.Sequence) всегда итерируемые, поскольку они реализуют метод __getitem__
Теперь посмотрим, что с итераторами в Python. Они представлены абстрактным классом collections.abc.Iterator:
__next__ Возвращает следующий доступный элемент и вызывает исключение StopIteration, когда элементов не осталось.
__iter__ Возвращает self. Это позволяет использовать итератор там, где ожидается итерируемых объект, например for.
__subclasshook__ Проверяет наличие у класса метода __iter__ и __next__
Итого, итератор в python — это любой объект, реализующий метод __next__ без аргументов, который должен вернуть следующий элемент или ошибку StopIteration. Также он реализует метод __iter__ и поэтому сам является итерируемым объектом.
Таким образом можно реализовать итерируемый объект на основе списка и его итератор:
Функция next() вызывает метод __next__. Ей можно передать второй аргумент который она будет возвращать по окончанию итерации вместо ошибки StopIteration.
Прежде чем переходить к генераторам, рассмотрим еще одну возможность встроенной функции iter(). Ее можно вызывать с двумя аргументами, что позволит создать из вызываемого объекта(функция или класс с реализованным методом __call__) итератор. Первый аргумент должен быть вызываемым объектом, а второй — неким ограничителем. Вызываемый объект вызывается на каждой итерации и итерирование завершается, когда возбуждается исключение StopIteration или возвращается значения ограничителя.
Например, из функции которая произвольно возвращает 1-6, можно сделать итератор, который будет возвращать значения пока не «выпадет» 6:
Небольшой класс ProgrammingLanguages, у которого есть кортеж c языками программирования, конструктор принимает начальное значения индекса по названию языка и функция __call__ которая перебирает кортеж.
Можем перебрать все языки начиная с C# и до последнего:
Генераторы
С точки зрения реализации, генератор в Python — это языковая конструкция, которую можно реализовать двумя способами: как функция с ключевым словом yield или как генераторное выражение. В результате вызова функции или вычисления выражения, получаем объект-генератор типа types.GeneratorType.
В объекте-генераторе определены методы __next__ и __iter__, то есть реализован протокол итератора, с этой точки зрения, в Python любой генератор является итератором.
Концептуально, итератор — это механизм поэлементного обхода данных, а генератор позволяет отложено создавать результат при итерации. Генератор может создавать результат на основе какого то алгоритма или брать элементы из источника данных(коллекция, файлы, сетевое подключения и пр) и изменять их.
Ярким пример являются функции range и enumerate:
range генерирует ограниченную арифметическую прогрессию целых чисел, не используя никакой источник данных.
enumerate генерирует двухэлементные кортежи с индексом и одним элементом из итерируемого объекта.
Yield
Для начало напишем простой генератор не используя объект-генератор. Это генератор чисел Фибоначчи:
Но используя ключевое слово yield можно сильно упростить реализацию:
Любая функция в Python, в теле которой встречается ключевое слово yield, называется генераторной функцией — при вызове она возвращает объект-генератор.
Объект-генератор реализует интерфейс итератора, соответственно с этим объектом можно работать, как с любым другим итерируемым объектом.
Рассмотрим работу yield:
Создается стейт-машина в которой при каждом вызове __next__ меняется состояния и в зависимости от него вызывается тот или иной кусок кода. Если в функции yield в цикле, то соответственно состояние стейт-машины зацикливается пока не будет выполнено условие.
Свой вариант range:
Генераторное выражение (generator expression)
Если кратко, то синтаксически более короткий способ создать генератор, не определяя и не вызывая функцию. А так как это выражение, то у него есть и ряд ограничений. В основном удобно использовать для генерации коллекций, их несложных преобразований и применений на них условий.
В языках программирования есть такие понятия, как ленивые/отложенные вычисления(lazy evaluation) и жадные вычисления(eager/greedy evaluation). Генераторы можно считать отложенным вычислением, в этом смысле списковое включение(list comprehension) очень похожи на генераторное выражение, но являются разными подходами.
Первый вариант работает схожим с нашей функцией cool_range образом и может генерировать без проблем любой диапазон. А вот второй вариант создаст сразу целый список, со всеми вытекающими от сюда проблемами.
Yield from
Для обхода ограниченно вложенных структур, традиционный подход использовать вложенные циклы. Тот же подход можно использовать когда генераторная функция должна отдавать значения, порождаемые другим генератором.
Функция похожая на itertools.chain:
Но вложенные циклы можно убрать, добавив конструкцию yield from:
Основная польза yield from в создании прямого канала между внутренним генератором и клиентом внешнего генератора. Но это уже больше тема про сопрограммы(coroutines), которые заслуживают отдельной статьи. Там же можно обсудить методы генератора: close(), throw() и send().
И в заключении еще один пример. Функция принимающая итерируемый объект, с любым уровнем вложенности другими итерируемыми объектами, и формирующая плоскую последовательность:
Итераторы и итерируемые объекты в Python
25.02.2019 23:05 CPython 3.7.2
На момент написания данной статьи только на одной странице Built-in Types официальной документации Python 3 слово “iterator” встречается 21 раз, а “iterable” – 39. Довольно популярные слова, не так ли? Сегодня мы попытаемся разобраться, что же они значат: что такое итератор (iterator) и что такое итерируемый объект (iterable), а также в чем между ними разница.
Итераторы
Рассмотрим такой пример:
Впрочем, мы можем получить тот же результат и без использования for :
Но если сейчас вызвать next() шестой раз, то мы получим исключение StopIteration :
Чтобы понять лучше, как работает for в данной ситуации, напишем его аналог, используя while :
Как и список, другие встроенные типы последовательностей (кортеж, строка и т.п.) также могут вернуть итератор:
Множества и словари также возвращают объект итератора:
Магические методы __iter__() и __next__()
name | address |
---|---|
Amazon.com, Inc. | 410 Terry Ave North, Seattle, Washington, U.S. |
Apple Inc. | 1 Apple Park Way, Cupertino, California, U.S. |
Facebook, Inc. | 1601 Willow Road, Menlo Park, California, U.S. |
Google LLC | 1600 Amphitheatre Parkway, Mountain View, California, U.S. |
Microsoft Corporation | One Microsoft Way, Redmond, Washington, U.S. |
… | … |
Сделаем первые наброски. Чтобы иметь возможность быстро тестировать написанный код, мы будем создавать в памяти временную базу данных (БД) с минимальным набором данных, не касаясь реальной БД:
Объект companies в дальнейшем будет возвращать итератор, но пока абсолютно бесполезен.
Сохраним наш код в файл “companies.py”. Уже сейчас мы можем импортировать класс Companies и использовать его в других модулях (например так: from companies import Companies ). В этом случае код блока условия ( if __name__ == ‘__main__’ ) не будет выполняться. Выполняется он только непосредственно при запуске модуля (например вот так: python3 companies.py ).
Итак, вот что получилось:
Теперь нам нужно сделать так, чтобы экземпляр Companies мог вернуть итератор. Для этого нужно реализовать метод __iter__() :
Результат работы будет таким:
Итерируемые объекты
Стоит заметить, что создавать отдельный класс итератора вовсе необязательно. Ничего не мешает нам совместить классы Companies и CompaniesIterator :
Теперь класс CompaniesIterator нам больше не нужен, так как Companies сам реализует протокол итератора. Сейчас экземпляр класса Companies будет как итерируемым объектом, так и итератором.
Магический метод __getitem__()
В случае с нашим MyList мы также получим объект итератора, используя iter() :
Встроенная функция iter()
Как уже упоминалось выше, итерируемый объект может иметь оба метода сразу. Давайте добавим метод __iter__() к уже реализованному ранее __getitem__() :
Итак, можно выделить несколько этапов работы iter() :
Дополнительный материал не по теме
Возвращаясь к примеру с базой данных SQLite, хотелось бы заметить, что реализация собственного итератора в данной ситуации избыточна, так как объект курсора сам является итератором. Мы можем просто возвращать его при вызове __iter__() :
Теперь мы можем легко получить информацию о количестве записей:
Дата последнего редактирования статьи: 06.06.2019 12:45
© 2019-2020, Dmitry Pakhomov
Except where otherwise noted, content on this site is licensed under a Creative Commons Attribution-ShareAlike 4.0 International License
Итерируемые объекты и итераторы: углублённое руководство по JavaScript
Эта статья представляет собой углублённое введение в итерируемые объекты (iterables) и итераторы (iterators) в JavaScript. Моя главная мотивация к её написанию заключалась в подготовке к изучению генераторов. По сути, я планировал позднее поэкспериментировать с комбинированием генераторов и хуками React. Если вам это интересно, то следите за моим Twitter или YouTube!
Вообще-то я планировал начать со статьи про генераторы, но вскоре стало очевидно, что о них сложно рассказывать без хорошего понимания итерируемых объектов и итераторов. На них мы сейчас и сосредоточимся. Буду исходить из того, что вы ничего не знаете по этой теме, но при этом мы значительно углубимся в неё. Так что если вы что-то знаете об итерируемых объектах и итераторах, но не чувствуете себя достаточно уверенно при их использовании, эта статья вам поможет.
Введение
Как вы заметили, мы обсуждаем итерируемые объекты и итераторы. Это взаимосвязанные, но разные концепции, так что при чтении статьи обращайте внимание, о какой из них идёт речь в конкретном случае.
Начнём с итерируемых объектов. Что это такое? Это нечто, что может быть итерировано, например:
Этот код выведет на экран:
То есть переменная element на каждом этапе итерации хранит массив из двух элементов. Первый из них — ключ, второй — значение.
Забавно, что конструктор Map опционально принимает итерируемые объекты пар ключ-значение. То есть это альтернативный способ конструирования такого же Map :
А поскольку Map является итерируемым объектом, мы можем очень легко создавать его копии:
Например, можно извлечь итератор массива:
А что такое итератор?
В консоли увидим объект < value: 1, done: false >. Первый элемент созданного нами массива — 1, и здесь она появилась в качестве значения. Также мы получили информацию, что итератор ещё не завершился, то есть мы можем пока вызывать функцию next и получать какие-то значения. Давайте попробуем! Вызовем next ещё два раза:
Этот код эквивалентен такому:
Создаём собственный итератор
Теперь мы знаем, что такое итерируемые объекты и итераторы. Возникает вопрос: «Можно ли написать собственные экземпляры?»
Давайте напишем собственные итераторы, сделав такой объект итерируемым!
Конструктор нашего класса берёт обычный объект и копирует его свойства в итерируемый объект (хотя на самом деле он ещё не является итерируемым!).
Создадим итерируемый объект:
Теперь можно писать настоящий итератор!
После каждого вызова next нужно возвращать объект вида < value, done >. Сделаем его с выдуманными значениями.
Учитывая такой итерируемый объект:
мы будем выводить пары ключ-значение, аналогично тому, как делает итерирование ES6 Map :
Теперь мы готовы вернуть фактическое значение в методе next :
Этот код почти работает. Осталось добавить только одно.
После наших изменений класс IterableObject выглядит так:
Работает! Вот что выводится в консоли:
Кроме того, мы можем настраивать, что именно должны возвращать такие итерации. Сейчас наш итератор возвращает пары ключ-значение. А если нам нужны только значения? Легко, просто перепишем итератор:
Мы вернули одни лишь значения объектов. Всё это доказывает гибкость самописных итераторов. Вы можете заставить их возвращать что угодно.
Итераторы как… итерируемые объекты
Люди очень часто путают итераторы и итерируемые объекты. Это ошибка, и я старался аккуратно разделить эти два понятия. Подозреваю, мне известна причина, по которой люди так часто их путают.
Оказывается, что итераторы… иногда являются итерируемыми объектами!
Проверить это можно, если взять итератор, возвращённый из массива, и вызвать применительно к нему [Symbol.iterator]() :
Если сравнить оба итератора с помощью ===, то окажется, что они совершенно одинаковы:
В результате этот фрагмент:
Во-первых, вам может потребоваться создать итератор без принадлежности к какому-нибудь итерируемому объекту. Мы рассмотрим этот пример ниже, и это не такая уж редкость. Иногда нам просто не нужнен сам итерируемый объект.
Этот код работает, потому что итераторы, возвращённые методами, являются и итерируемыми объектами. В противном случае пришлось бы, скажем, обёртывать результат вызова map.entries() в какой-нибудь дурацкий итерируемый объект. К счастью, нам это делать не нужно.
Состояние итератора
Теперь должно быть очевидно, что у каждого итератора есть ассоциированное с ним состояние. Например, в итераторе IterableObject мы хранили состояние — переменную index — в виде замыкания. И обновляли её после каждого этапа итерации.
Давайте вручную вызовем next после завершения цикла:
Вот оно что. После завершения цикла итератор переходит в состояние «done». Теперь он всегда будет возвращать объект < value: undefined, done: true >.
Теперь всё работает так, как ожидается. Давайте разберёмся, почему работает многократный прямой циклический проход по массиву:
Итераторы и массивы
И когда говорят, что у массива есть длина, имеют в виду, что эта длина конечна. В JavaScript не бывает бесконечных массивов.
Эти два качества указывают на жадность массивов.
А итераторы ленивые.
Чтобы показать это, создадим два своих итератора: первый будет бесконечным, в отличие от конечных массивов, а второй будет инициализировать свои значения только тогда, когда они будут запрошены пользователем итератора.
Начнём с бесконечного итератора. Звучит пугающе, но создать его очень просто: итератор начинается с 0 и на каждом этапе возвращает следующее число в последовательности. Вечно.
И последнее: это случай, о котором я упоминал выше — мы создали итератор, но для работы ему не нужен итерируемый «родитель».
После запуска мы увидим в консоли:
Мы действительно создали бесконечный итератор, возвращающий столько чисел, сколько пожелаете. И сделать его было очень легко!
Теперь напишем итератор, который не создаёт значения, пока они не будут запрошены.
Ну… мы уже его сделали!
Это может выглядеть довольно бесполезным. Ведь числа создаются быстро и не занимают много места в памяти. Но если вы работаете с очень большими объектами, занимающими много памяти, то иногда замена массивов на итераторы может быть очень полезной, ускорив программу и сэкономив память.
Чем больше объект (или чем дольше он создаётся), тем больше выгода.
Другие способы использования итераторов
Мы уже видели, что конструктор Map принимает итерируемые объекты в качестве аргумента. Вы также можете с помощью метода Array.from легко преобразовать итерируемый объект в массив. Но будьте осторожны! Как я говорил, ленивость итератора иногда может быть большим преимуществом. Преобразование в массив лишает ленивости. Все значения, возвращаемые итератором, начинают инициализироваться немедленно, а затем помещаются в массив. Это означает, что если мы попробуем преобразовать бесконечный counterIterator в массив, то это приведёт к катастрофе. Array.from будет исполняться вечно без возвращения результата. Так что перед преобразованием итерируемого объекта/итератора в массив нужно убедиться в безопасности операции.
Также можно получить из итерируемого объекта все значения и применить их к функции:
Итерируемые объекты и итераторы: углублённое руководство по JavaScript
Эта статья представляет собой углублённое введение в итерируемые объекты (iterables) и итераторы (iterators) в JavaScript. Моя главная мотивация к её написанию заключалась в подготовке к изучению генераторов. По сути, я планировал позднее поэкспериментировать с комбинированием генераторов и хуками React. Если вам это интересно, то следите за моим Twitter или YouTube!
Вообще-то я планировал начать со статьи про генераторы, но вскоре стало очевидно, что о них сложно рассказывать без хорошего понимания итерируемых объектов и итераторов. На них мы сейчас и сосредоточимся. Буду исходить из того, что вы ничего не знаете по этой теме, но при этом мы значительно углубимся в неё. Так что если вы что-то знаете об итерируемых объектах и итераторах, но не чувствуете себя достаточно уверенно при их использовании, эта статья вам поможет.
Введение
Как вы заметили, мы обсуждаем итерируемые объекты и итераторы. Это взаимосвязанные, но разные концепции, так что при чтении статьи обращайте внимание, о какой из них идёт речь в конкретном случае.
Начнём с итерируемых объектов. Что это такое? Это нечто, что может быть итерировано, например:
Этот код выведет на экран:
То есть переменная element на каждом этапе итерации хранит массив из двух элементов. Первый из них — ключ, второй — значение.
Забавно, что конструктор Map опционально принимает итерируемые объекты пар ключ-значение. То есть это альтернативный способ конструирования такого же Map :
А поскольку Map является итерируемым объектом, мы можем очень легко создавать его копии:
Например, можно извлечь итератор массива:
А что такое итератор?
В консоли увидим объект < value: 1, done: false >. Первый элемент созданного нами массива — 1, и здесь она появилась в качестве значения. Также мы получили информацию, что итератор ещё не завершился, то есть мы можем пока вызывать функцию next и получать какие-то значения. Давайте попробуем! Вызовем next ещё два раза:
Этот код эквивалентен такому:
Создаём собственный итератор
Теперь мы знаем, что такое итерируемые объекты и итераторы. Возникает вопрос: «Можно ли написать собственные экземпляры?»
Давайте напишем собственные итераторы, сделав такой объект итерируемым!
Конструктор нашего класса берёт обычный объект и копирует его свойства в итерируемый объект (хотя на самом деле он ещё не является итерируемым!).
Создадим итерируемый объект:
Теперь можно писать настоящий итератор!
После каждого вызова next нужно возвращать объект вида < value, done >. Сделаем его с выдуманными значениями.
Учитывая такой итерируемый объект:
мы будем выводить пары ключ-значение, аналогично тому, как делает итерирование ES6 Map :
Теперь мы готовы вернуть фактическое значение в методе next :
Этот код почти работает. Осталось добавить только одно.
После наших изменений класс IterableObject выглядит так:
Работает! Вот что выводится в консоли:
Кроме того, мы можем настраивать, что именно должны возвращать такие итерации. Сейчас наш итератор возвращает пары ключ-значение. А если нам нужны только значения? Легко, просто перепишем итератор:
Мы вернули одни лишь значения объектов. Всё это доказывает гибкость самописных итераторов. Вы можете заставить их возвращать что угодно.
Итераторы как… итерируемые объекты
Люди очень часто путают итераторы и итерируемые объекты. Это ошибка, и я старался аккуратно разделить эти два понятия. Подозреваю, мне известна причина, по которой люди так часто их путают.
Оказывается, что итераторы… иногда являются итерируемыми объектами!
Проверить это можно, если взять итератор, возвращённый из массива, и вызвать применительно к нему [Symbol.iterator]() :
Если сравнить оба итератора с помощью ===, то окажется, что они совершенно одинаковы:
В результате этот фрагмент:
Во-первых, вам может потребоваться создать итератор без принадлежности к какому-нибудь итерируемому объекту. Мы рассмотрим этот пример ниже, и это не такая уж редкость. Иногда нам просто не нужнен сам итерируемый объект.
Этот код работает, потому что итераторы, возвращённые методами, являются и итерируемыми объектами. В противном случае пришлось бы, скажем, обёртывать результат вызова map.entries() в какой-нибудь дурацкий итерируемый объект. К счастью, нам это делать не нужно.
Состояние итератора
Теперь должно быть очевидно, что у каждого итератора есть ассоциированное с ним состояние. Например, в итераторе IterableObject мы хранили состояние — переменную index — в виде замыкания. И обновляли её после каждого этапа итерации.
Давайте вручную вызовем next после завершения цикла:
Вот оно что. После завершения цикла итератор переходит в состояние «done». Теперь он всегда будет возвращать объект < value: undefined, done: true >.
Теперь всё работает так, как ожидается. Давайте разберёмся, почему работает многократный прямой циклический проход по массиву:
Итераторы и массивы
И когда говорят, что у массива есть длина, имеют в виду, что эта длина конечна. В JavaScript не бывает бесконечных массивов.
Эти два качества указывают на жадность массивов.
А итераторы ленивые.
Чтобы показать это, создадим два своих итератора: первый будет бесконечным, в отличие от конечных массивов, а второй будет инициализировать свои значения только тогда, когда они будут запрошены пользователем итератора.
Начнём с бесконечного итератора. Звучит пугающе, но создать его очень просто: итератор начинается с 0 и на каждом этапе возвращает следующее число в последовательности. Вечно.
И последнее: это случай, о котором я упоминал выше — мы создали итератор, но для работы ему не нужен итерируемый «родитель».
После запуска мы увидим в консоли:
Мы действительно создали бесконечный итератор, возвращающий столько чисел, сколько пожелаете. И сделать его было очень легко!
Теперь напишем итератор, который не создаёт значения, пока они не будут запрошены.
Ну… мы уже его сделали!
Это может выглядеть довольно бесполезным. Ведь числа создаются быстро и не занимают много места в памяти. Но если вы работаете с очень большими объектами, занимающими много памяти, то иногда замена массивов на итераторы может быть очень полезной, ускорив программу и сэкономив память.
Чем больше объект (или чем дольше он создаётся), тем больше выгода.
Другие способы использования итераторов
Мы уже видели, что конструктор Map принимает итерируемые объекты в качестве аргумента. Вы также можете с помощью метода Array.from легко преобразовать итерируемый объект в массив. Но будьте осторожны! Как я говорил, ленивость итератора иногда может быть большим преимуществом. Преобразование в массив лишает ленивости. Все значения, возвращаемые итератором, начинают инициализироваться немедленно, а затем помещаются в массив. Это означает, что если мы попробуем преобразовать бесконечный counterIterator в массив, то это приведёт к катастрофе. Array.from будет исполняться вечно без возвращения результата. Так что перед преобразованием итерируемого объекта/итератора в массив нужно убедиться в безопасности операции.
Также можно получить из итерируемого объекта все значения и применить их к функции: