Ошибки, сложный материал, вопросы >
Нашли опечатку или неточность?

Выделите текст, нажмите ctrl + enter и отправьте его нам. В течение нескольких дней мы исправим ошибку или улучшим формулировку.

Что-то не получается или материал кажется сложным?

Загляните в раздел «Обсуждение»:

  • задайте вопрос нашим менторам. Вы быстрее справитесь с трудностями и прокачаете навык постановки правильных вопросов, что пригодится и в учёбе, и в работе программистом;
  • расскажите о своих впечатлениях. Если курс слишком сложный, подробный отзыв поможет нам сделать его лучше;
  • изучите вопросы других учеников и ответы на них. Это база знаний, которой можно и нужно пользоваться.
Об обучении на Хекслете

Итераторы и Генераторы

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

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

// Упадет с ошибкой
for (const v of { a: 1, b: 2 }) {
  console.log(v);
}

// obj это объект и for спокойно по нему пройдется
const obj = makeIterator(); // Устройство функции makeIterator будет раскрыто позже
for (const v of obj) {
  console.log(v);
}

Во втором примере, obj представляет из себя итерируемый объект (iterable object). Мы можем самостоятельно сделать его таким:

const obj = {
  collection: ['yo', 'ya'],
  [Symbol.iterator]: makeIterator,
};

for (const v of obj) {
  console.log(v);
}
// yo
// ya

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

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

Эта функция не является общей для всех итерируемых объектов, ее содержимое зависит от объектов, для которых она предназначена. Общее правило - функция должна реализовывать The iterable protocol.

const makeIterator = function () {
  let nextIndex = 0;

  const next = () => {
    if (nextIndex < this.collection.length) {
      const value = this.collection[nextIndex++];
      return { value, done: false };
    }
    return { done: true };
  };
  return { next };
};

Функция makeIterator не имеет параметров, потому что так она вызывается внутри js. Из этого следует, что доступ к текущему объекту, к которому она прикреплена, возможен только через this, а значит она должна быть объявлена как functionDeclaration, а не arrowFunction. Требование к возвращаемому значению этой функции следующее:

Необходимо вернуть объект с методом next. Каждый вызов next будет возвращать объект с двумя свойствами: value и done. value – это значение текущего элемента коллекции, а done – это флаг конца коллекции. Как только next завершает перебор, то возвращается { done: true }, и это является сигналом к тому, что итерирование завершено.

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

const makeIterator = coll => {
  let nextIndex = 0;

  const next = () => {
    if (nextIndex < coll.length) {
      const value = coll[nextIndex++];
      return { value, done: false };
    }
    return { done: true };
  };
  return { next };
}

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

const it = makeIterator(['yo', 'ya']);
it.next(); // { value: 'yo', done: false }
it.next(); // { value: 'ya', done: false }
it.next(); // { done: true }

Именно так будет вызываться next скрыто от наших глаз в момент итерации по объекту.

Что можно заметить, глядя на эту функцию? Она содержит в себе скрытое состояние, которое необходимо для запоминания текущей позиции. Как мы помним, состояние – штука сложная, и, программируя в таком стиле, легко допустить ошибку.

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

const makeIterator = function* (coll) {
  for (const value of coll) {
    yield value;
  }
};

const it = makeIterator(['yo', 'ya']);
it.next(); // { value: 'yo', done: false }
it.next(); // { value: 'ya', done: false }
it.next(); // { value: undefined, done: true }

Как видно из примера, генераторы вводят новый синтаксис в язык. Во-первых, это звездочка после слова function. Она просто указывает на то, что мы имеем дело с генератором. Во-вторых, выражение yield (подчеркиваю: это – не инструкция).

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

Еще один пример для осознания:

const gen = function* () {
  yield 1;
  yield 2;
  yield 3;
};

const it = gen();
it.next(); // { value: 1, done: false }
it.next(); // { value: 2, done: false }
it.next(); // { value: 3, done: false }
it.next(); // { value: undefined, done: true }

Или даже так:

const it = gen();
[...it]; // [1, 2, 3]

Кроме yield в генераторах можно использовать версию yield*, которая ожидает на вход коллекцию и делает yield для каждого элемента этой коллекции.

const makeIterator = function* () {
  yield* this.collection;
};

Теперь можно переписать наш первый пример вот таким образом:

const obj = {
  collection: ['yo', 'ya'],
  [Symbol.iterator]: function* () {
    yield* this.collection;
  },
};

for (const v of obj) {
  console.log(v);
}
// yo
// ya

<span class="translation_missing" title="translation missing: ru.web.courses.lessons.mentors.mentor_avatars">Mentor Avatars</span>

Остались вопросы? Задайте их в разделе «Обсуждение»

Вам ответят менторы из команды Хекслета или другие студенты.

Для полного доступа к курсу нужна профессиональная подписка

Профессиональная подписка откроет полный доступ ко всем курсам, упражнениям и урокам Хекслета, даст возможность обращаться за помощью к менторам и пожизненный доступ к теории пройденных уроков. Подписку можно отменить в любой момент.

Получить доступ
115
курсов
892
упражнения
2241
час теории
3196
тестов

Зарегистрироваться

или войти в аккаунт

Курсы программирования для новичков и опытных разработчиков. Начните обучение бесплатно.

  • 115 курсов, 2000+ часов теории
  • 800 практических заданий в браузере
  • 250 000 студентов

Нажимая кнопку «Зарегистрироваться», вы даёте своё согласие на обработку персональных данных в соответствии с «Политикой конфиденциальности» и соглашаетесь с «Условиями оказания услуг». Защита от спама reCAPTCHA «Конфиденциальность» и «Условия использования».

Наши выпускники работают в компаниях:

Логотип компании Альфа Банк
Логотип компании Rambler
Логотип компании Bookmate
Логотип компании Botmother

Есть вопрос или хотите участвовать в обсуждении?

Зарегистрируйтесь или войдите в свой аккаунт

Нажимая кнопку «Зарегистрироваться», вы даёте своё согласие на обработку персональных данных в соответствии с «Политикой конфиденциальности» и соглашаетесь с «Условиями оказания услуг». Защита от спама reCAPTCHA «Конфиденциальность» и «Условия использования».