Итераторы и генераторы JavaScript: полное руководство

От автора: с появлением ES6 итераторы и генераторы были официально добавлены в JavaScript. Итераторы позволяют перебирать любой объект, соответствующий спецификации. В первом разделе мы увидим, как использовать итераторы и выполнить итерацию любого объекта. Вторая часть этой статьи полностью посвящена генераторам: что это такое, как их использовать и в каких ситуациях они могут быть полезны.

Я люблю разбираться в том, как все работает под капотом: в предыдущей серии блогов я объяснил, как JavaScript работает в браузере. В продолжение этой статьи я хочу объяснить, как работают итераторы и генераторы JavaScript.

Что такое итераторы?

Прежде чем мы сможем понять генераторы, нам необходимо доскональное понимание итераторов в JavaScript, поскольку эти две концепции тесно связаны между собой. После этого раздела станет ясно, что генераторы — это просто способ более безопасного написания итераторов. Как уже понятно из названия, итераторы позволяют выполнять итерацию по объекту (массивы также являются объектами). Скорее всего, вы уже использовали итераторы JavaScript. Каждый раз, когда вы выполняете итерацию по массиву, например, вы использовали итераторы, но вы также можете выполнять итерацию по объектам Map и даже по строкам.

JavaScript. Быстрый старт

Изучите основы JavaScript на практическом примере по созданию веб-приложения

Узнать подробнее

Для любого объекта, реализующего итеративный протокол, можно выполнить итерацию, используя «for… of».
Копнув немного глубже, вы можете сделать любой объект итеративным, реализовав функцию @@ iterator, которая возвращает объект-итератор.

Создадим итерацию любого обекта

Чтоб понять это правильно, вероятно, лучше всего взглянуть на пример создания итерации обычного объекта. Начнем с объекта, который содержит имена пользователей, сгруппированные по городам:

Я взял этот пример, потому что нелегко перебрать пользователей, если данные структурированы таким образом; для этого нам понадобится несколько циклов, чтобы охватить всех пользователей. Если мы попытаемся перебрать этот объект как есть, мы получим следующее сообщение об ошибке: Uncaught ReferenceError: iterator is not defined.

Чтобы сделать этот объект итеративным, нам сначала нужно добавить функцию @@iterator. Мы можем получить доступ к этому символу через Symbol.iterator.

Как я упоминал ранее, функция итератора возвращает объект итератора. Объект содержит функцию next, которая также возвращает объект с двумя атрибутами: done и value.

Value содержит текущее значение итерации, а done — это логическое значение, которое сообщает нам, завершено ли выполнение. При реализации этой функции мы должны быть особенно осторожны со значением done, поскольку оно всегда возвращает false, что приведет к бесконечному циклу. В приведенном выше примере кода уже представлена правильная реализация итеративного протокола. Мы можем проверить это, вызвав функцию next объекта-итератора.

Обход объекта с помощью «for… of» неявно использует функцию next. Использование «for… of» в этом случае ничего не вернет, потому что мы сразу установили для done значение false. Мы также не получаем никаких имен пользователей, реализуя его таким образом, поэтому мы изначально хотели сделать этот объект iterable.

Реализация функции итератора

Прежде всего, нам нужно получить доступ к ключам объекта, представляющего города. Мы можем получить это, вызвав Object.keys для ключевого слова this, которое относится к родительскому элементу функции, которым в данном случае является объект userNamesGroupedByLocation. Мы можем получить доступ к ключам через this, только если мы определили итеративную функцию с ключевым словом function.

Нам также нужны две переменные, которые отслеживают наши итерации.

Мы определяем эти переменные в функции итератора, но вне функции next, что позволяет нам сохранять данные между итерациями.

В функции next нам сначала нужно получить массив пользователей текущего города и текущего пользователя, используя определенные ранее индексы. Теперь мы можем использовать эти данные для изменения возвращаемого значения.

Затем нам нужно увеличивать индексы на каждой итерации. Мы увеличиваем индекс пользователя каждый раз, если мы не добрались до последнего пользователя данного города, и в этом случае мы установим userIndex равным 0 и вместо этого увеличим индекс города.

Будьте осторожны, не осуществляйте проход по этому объекту с помощью «for… of». Учитывая, что done всегда равно false, это приведет к бесконечному циклу.

Последнее, что нам нужно добавить, — это условие выхода, которое устанавливает для done значение true. Мы выходим из цикла после того, как перебрали все города.

После того, как мы объединим все вместе, наша функция будет выглядеть следующим образом:

Это позволяет нам быстро получить все имена из нашего объекта с помощью цикла «for… of».

Как видите, создание итерации объекта — не волшебство. Однако делать это нужно очень осторожно, потому что ошибки в функции next могут легко привести к бесконечному циклу.

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

Добавьте к объекту функцию итератора с помощью ключа @@iterator (доступного через Symbol.iterator)

Эта функция возвращает объект, который включает функцию next

Функция next возвращает объект с атрибутами done и value.

Что такое генераторы?

Мы узнали, как сделать итерацию по любому объекту, но как это связано с генераторами? Хотя итераторы — мощный инструмент, их не часто создают, как в примере выше. Нам нужно быть очень осторожными при программировании итераторов, поскольку ошибки могут иметь серьезные последствия, а управление внутренней логикой может быть сложной задачей.

Генераторы — полезный инструмент, который позволяет нам создавать итераторы, определяя функцию. Такой подход менее подвержен ошибкам и позволяет более эффективно создавать итераторы.

Важной характеристикой генераторов и итераторов является то, что они позволяют останавливать и продолжать выполнение по мере необходимости. В этом разделе мы увидим несколько примеров, в которых используется эта функция.

JavaScript. Быстрый старт

Изучите основы JavaScript на практическом примере по созданию веб-приложения

Узнать подробнее

Объявление функции генератора

Создание функции генератора очень похоже на создание обычных функций. Все, что нам нужно сделать, это добавить звездочку (*) перед именем.

Если мы хотим создать анонимную функцию-генератор, эта звездочка переместится в конец ключевого слова function.

Использование ключевого слова yield

Объявление функции генератора — это только половина работы и само по себе не очень полезно. Как уже упоминалось, генераторы — это более простой способ создания итераций. Но как итератор узнает, какую часть функции он должен выполнить? Должен ли он повторяться по каждой строке?

Вот где в игру вступает ключевое слово yield. Мы можем добавить это ключевое слово в каждую строку, где мы хотим, чтобы итерация остановилась. Функция next затем вернет результат оператора этой строки как часть объекта итератора ({ done: false, value: ‘something’ }).

Вывод этого кода будет следующим:

Вызов stringGenerator сам по себе ничего не сделает, потому что он автоматически остановит выполнение при первом операторе yield. Когда функция достигает своего конца, value становится undefined, а done автоматически устанавливается в true.

Использование yield *

Если мы добавим звездочку к ключевому слову yield, мы делегируем выполнение другому объекту итератора. Например, мы могли бы использовать это для делегирования другой функции или массиву:

Результат выполнения:

Передача значений генераторам

Функция next, которую итератор возвращает для генераторов, имеет дополнительную особенность: она позволяет перезаписать возвращаемое значение. Взяв предыдущий пример, мы можем переопределить значение, которое возвращает yield.

Нам нужно вызвать next один раз перед передачей значения для запуска генератора.

Методы генераторов

Помимо метода «next», который требуется любому итератору, генераторы также предоставляют функции return и throw.

Возвращаемая функция

Вызов return вместо next на итераторе приведет к завершению цикла на следующей итерации. Каждая итерация, которая происходит после вызова return, установит для done значение true, а для value значение undefined.

Если мы передадим значение этой функции, она заменит атрибут value в объекте итератора. Этот пример из документации Web MDN прекрасно это иллюстрирует:

Функция throw

Генераторы также реализуют функцию throw, которая вместо продолжения цикла выдает ошибку и прекращает выполнение:

Результат приведенного выше кода следующий:

Если мы попытаемся повторить итерацию после выдачи ошибки, возвращаемое значение будет неопределенным, а для done будет установлено значение true.

Зачем использовать генераторы?

Как мы видели в этой статье, мы можем использовать генераторы для создания итераций. Тема может показаться очень абстрактной, и я должен признать, что сам мне редко приходится пользоваться генераторами.

Однако в некоторых случаях эта функция очень полезна. В этих случаях обычно используется тот факт, что вы можете приостанавливать и возобновлять выполнение генераторов.

Генератор уникальных идентификаторов

Это мой любимый вариант использования, потому что он идеально подходит для генераторов. Для создания уникальных и дополнительных идентификаторов необходимо отслеживать созданные идентификаторы.

С помощью генератора вы можете создать бесконечный цикл, который создает новый идентификатор при каждой итерации.

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

Другие варианты использования генераторов

Есть много других вариантов использования. Многие библиотеки используют генераторы, например, Mobx-State-Tree или Redux-Saga.

Заключение

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

В этой статье мы узнали об итераторах и о том, как сделать итерацию любого объекта. Во второй части мы узнали, что такое генераторы, как их использовать и в каких ситуациях мы можем их использовать.

Если вы хотите узнать больше о том, как JavaScript работает «под капотом», вы можете ознакомиться с моей серией блогов о том, как JavaScript работает в браузере, с объяснением цикла событий и управления памятью JavaScript.

Автор: Felix Gerschau

Источник: blog.logrocket.com

Редакция: Команда webformyself.

Читайте нас в Telegram, VK, Яндекс.Дзен

JavaScript. Быстрый старт

Изучите основы JavaScript на практическом примере по созданию веб-приложения

Узнать подробнее

Full-Stack практика. Создание JavaScript блога

Создание веб-приложения с нуля на JavaScript, NodeJS, ExpressJS

Смотреть

Метки:

Похожие статьи:

Комментарии Вконтакте:

Комментарии Facebook:

Комментирование закрыто.