0 added
0 removed
Original
2026-01-01
Modified
2026-02-26
1
<p>Этот урок посвящен механизму, основное предназначение которого не связано с асинхронным кодом. Этот механизм называется генераторами и, фактически, представляет из себя улучшенный итератор. А поскольку с итераторами мы тоже не знакомы, то начнем наше повествование с них.</p>
1
<p>Этот урок посвящен механизму, основное предназначение которого не связано с асинхронным кодом. Этот механизм называется генераторами и, фактически, представляет из себя улучшенный итератор. А поскольку с итераторами мы тоже не знакомы, то начнем наше повествование с них.</p>
2
<p>Итак, вообразим следующую задачу: необходимо сделать объект, содержащий в себе коллекцию (чего угодно), итерируемым. На практике это означает, что мы можем идти циклом for...of по самому объекту, хотя, как мы знаем, по умолчанию это невозможно. Пример:</p>
2
<p>Итак, вообразим следующую задачу: необходимо сделать объект, содержащий в себе коллекцию (чего угодно), итерируемым. На практике это означает, что мы можем идти циклом for...of по самому объекту, хотя, как мы знаем, по умолчанию это невозможно. Пример:</p>
3
<p>Во втором примере, obj представляет из себя итерируемый объект (iterable object). Мы можем самостоятельно сделать его таким:</p>
3
<p>Во втором примере, obj представляет из себя итерируемый объект (iterable object). Мы можем самостоятельно сделать его таким:</p>
4
<p>Напомню, что Symbol - это специальный неизменяемый тип данных. В основном используется в свойствах объектов. js предоставляет несколько встроенных символов, одним из которых и является iterator.</p>
4
<p>Напомню, что Symbol - это специальный неизменяемый тип данных. В основном используется в свойствах объектов. js предоставляет несколько встроенных символов, одним из которых и является iterator.</p>
5
<p>Чтобы сделать любой объект итерируемым, нужно создать свойство Symbol.iterator и записать туда специальную функцию, о структуре которой мы сейчас и поговорим.</p>
5
<p>Чтобы сделать любой объект итерируемым, нужно создать свойство Symbol.iterator и записать туда специальную функцию, о структуре которой мы сейчас и поговорим.</p>
6
<p>Эта функция не является общей для всех итерируемых объектов, ее содержимое зависит от объектов, для которых она предназначена. Общее правило - функция должна реализовывать The iterable protocol.</p>
6
<p>Эта функция не является общей для всех итерируемых объектов, ее содержимое зависит от объектов, для которых она предназначена. Общее правило - функция должна реализовывать The iterable protocol.</p>
7
<p>Функция makeIterator не имеет параметров, потому что так она вызывается внутри js. Из этого следует, что доступ к текущему объекту, к которому она прикреплена, возможен только через this, а значит она должна быть объявлена как functionDeclaration, а не arrowFunction. Требование к возвращаемому значению этой функции следующее:</p>
7
<p>Функция makeIterator не имеет параметров, потому что так она вызывается внутри js. Из этого следует, что доступ к текущему объекту, к которому она прикреплена, возможен только через this, а значит она должна быть объявлена как functionDeclaration, а не arrowFunction. Требование к возвращаемому значению этой функции следующее:</p>
8
<p>Необходимо вернуть объект с методом next. Каждый вызов next будет возвращать объект с двумя свойствами: value и done. Свойство value - это значение текущего элемента коллекции, а done - это флаг конца коллекции. Как только next завершает перебор, то возвращается { done: true }, и это является сигналом, что итерирование завершено.</p>
8
<p>Необходимо вернуть объект с методом next. Каждый вызов next будет возвращать объект с двумя свойствами: value и done. Свойство value - это значение текущего элемента коллекции, а done - это флаг конца коллекции. Как только next завершает перебор, то возвращается { done: true }, и это является сигналом, что итерирование завершено.</p>
9
<p>Можно продемонстрировать работу этой функции, немного переписав ее для возможности прямого вызова:</p>
9
<p>Можно продемонстрировать работу этой функции, немного переписав ее для возможности прямого вызова:</p>
10
<p>Еще раз отмечу, что выше мы создали пример только для демонстрации. В реальном коде такая функция не сделает объект итерируемым.</p>
10
<p>Еще раз отмечу, что выше мы создали пример только для демонстрации. В реальном коде такая функция не сделает объект итерируемым.</p>
11
<p>Именно так будет вызываться next, скрыто от наших глаз в момент итерации по объекту.</p>
11
<p>Именно так будет вызываться next, скрыто от наших глаз в момент итерации по объекту.</p>
12
<p>Что можно заметить, глядя на эту функцию? Она содержит в себе скрытое состояние, которое необходимо для запоминания текущей позиции. Как мы помним, состояние - штука сложная, и программируя в таком стиле, легко допустить ошибку.</p>
12
<p>Что можно заметить, глядя на эту функцию? Она содержит в себе скрытое состояние, которое необходимо для запоминания текущей позиции. Как мы помним, состояние - штука сложная, и программируя в таком стиле, легко допустить ошибку.</p>
13
<p>Оказывается, что можно переложить задачу по управлению состоянием на машину. Делается это с помощью так называемых генераторов.</p>
13
<p>Оказывается, что можно переложить задачу по управлению состоянием на машину. Делается это с помощью так называемых генераторов.</p>
14
<p>Как видно из примера, генераторы вводят новый синтаксис в язык. Во-первых, это звездочка после слова function. Она просто указывает на то, что мы имеем дело с генератором. Во-вторых, выражение yield (подчеркиваю: это - не инструкция).</p>
14
<p>Как видно из примера, генераторы вводят новый синтаксис в язык. Во-первых, это звездочка после слова function. Она просто указывает на то, что мы имеем дело с генератором. Во-вторых, выражение yield (подчеркиваю: это - не инструкция).</p>
15
<p>Генератор в отличие от обычной функции при своем вызове не выполняет тело, а возвращает специальный объект с методом next. Каждый раз, когда вызывается next, запускается тело генератора с того места, где оно остановилось последний раз. При первом вызове выполнение идет с самого начала генератора и продолжается до встречи с выражением yield. В этот момент управление передается наружу, next возвращает то, что было передано в yield, а генератор замирает в этом состоянии, на выражении yield. Последующие вызовы начинают работу от yield.</p>
15
<p>Генератор в отличие от обычной функции при своем вызове не выполняет тело, а возвращает специальный объект с методом next. Каждый раз, когда вызывается next, запускается тело генератора с того места, где оно остановилось последний раз. При первом вызове выполнение идет с самого начала генератора и продолжается до встречи с выражением yield. В этот момент управление передается наружу, next возвращает то, что было передано в yield, а генератор замирает в этом состоянии, на выражении yield. Последующие вызовы начинают работу от yield.</p>
16
<p>Еще один пример для осознания:</p>
16
<p>Еще один пример для осознания:</p>
17
<p>Или даже так:</p>
17
<p>Или даже так:</p>
18
<p>Кроме yield в генераторах можно использовать версию yield*, которая ожидает на вход коллекцию и делает yield для каждого элемента этой коллекции.</p>
18
<p>Кроме yield в генераторах можно использовать версию yield*, которая ожидает на вход коллекцию и делает yield для каждого элемента этой коллекции.</p>
19
<p>Теперь можно переписать наш первый пример вот таким образом:</p>
19
<p>Теперь можно переписать наш первый пример вот таким образом:</p>
20
20