Записки погромиста

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

Генераторы в JavaScript. Часть 1.

Если мы посмотрим что выводит в терминал console.log(Array.prototype)  мы увидим, что одно из свойств прототипа — Symbol.iterator, это функция, которая вернет нам итератор.

Например, вызов этой функции на массиве вернет нам Array Iterator {}.

const chars = ["A", "B", "C", "D"];
const iterator = chars[Symbol.iterator]();
console.log(iterator);

У итератора есть метод  .next(), который вернет нам первый элемент массива {value: "A", done: false} и указание на то остались ли элементы массива, который итератором еще не пройдены — done: false.

Последний вызов метода .next() вернет такой объект {value: undefined, done: true}. Данный результат будет нам возвращен сколько раз бы мы не вызывали next() так как итератор уже закончил свою работу. 

Создаем итератор.

Для начала создадим переменную для хранения текущего значения итератора и функцию next(), которая будет возвращать объект, содержащий текущее значение, которое увеличивается инкрементом и булев флаг, который станет равен false по окончании итерации.

let i = 0;
const next = () => ({
  value: i++,
  done: i > 10
});

Теперь при вызове метода next() мы будем получать такой результат:

next()
{value: 0, done: false}
next()
{value: 1, done: false}
next()
{value: 2, done: false}
next()
{value: 3, done: false}
next()
{value: 4, done: false}
next()
{value: 5, done: false}
next()
{value: 6, done: false}
next()
{value: 7, done: false}
next()
{value: 8, done: false}
next()
{value: 9, done: false}
next()
{value: 10, done: true}

Но что бы заставить нащ метод работать в цикле, нужно еще немного магии — создать объект в котором вычисляемое свойство Symbol.iterator будет методом, который возвращает метод next()

const iterator = {
  [Symbol.iterator]() {
    return {
      next
    }
  }
}

Теперть мы можем использовать итератор в цикле for:

for (let value of iterator) {
  console.log(value)
}

Так же можно использовать этот код для создания обратного итератора:

const abcs = ["A", "B", "C"];
const numbers = [1, 2, 3];

const createReverseIterator = array => ({
  [Symbol.iterator](){
  let i = array.length
    return{
      next: () => ({
        value: array[--i],
        done: i < 0
        })
      }
   }
})

Создаем генератор.

Для создания функции-генератора, будем использовать синтаксис, который превращает обычную функцию в генератор — function* (array) и ключевое слово yield которое будет возвращать значение каждый раз при вызове, но не прерывать цикл. Использование генераторов дает тот же результат при гораздо более лаконичном коде.

const reverseIterator = function* (array) {
    let i = array.length
    while (i > 0) {
        yield array[--i]
    }
}

Останавливаем генератор.

Вызов метода return() остановит итератор и установит свойству done значение true.

const numbers = [1, 2, 3]

const reverseIterator = function* (array) {
    let i = array.length
    while (i > 0) {
        yield array[--i]
    }
}

const iterator = reverseIterator(numbers);

console.log(iterator.next())
console.log(iterator.return())
console.log(iterator.next())

Результат вызова:

  • [object Object] {
      done: false,
      value: 3
    }
  • [object Object] {
      done: true,
      value: undefined
    }
  • [object Object] {
      done: true,
      value: undefined
    }

Так же мы можем использовать ключевое слово yeild c астериксом — yeild*, что вернет по отдельности каждый из элементов перечисляемого объекта, будь то строка, массив или что-то еще.

const abcs = ["A", "B", "C"]

const reverseIterator = function* (array) {
    yield* array
    yield* array.map(letter => letter.toLowerCase())
}

const iterator = reverseIterator(abcs)

for (let value of iterator) {
    console.log(value)
}

Что вернет такой резульат:

  • «A»
  • «B»
  • «C»
  • «a»
  • «b»
  • «c»

Published by

Оставьте комментарий