{wcademy}

Что такое итераторы и как их использовать в JavaScript/TypeScript?

September 08, 2020

Можно ли итерироваться по собственному классу в Typescript? Можно, если у класса есть метод [Symbol.iterator], который возвращает итератор. О них и поговорим.

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

Что бы объект мог считаться итератором, он должен иметь метод next(), который возвращает объект {value, done}, где value — следующее значение в последовательности, а done — булев флаг, показывающий закончились ли уже значения или нет.

Давайте решим классическую задачу с собеседований — напишем итератор, который возвращает последовательность Фибоначчи.

class Fib {
  private a = 0
  private b = 1;

  [Symbol.iterator]() {
    return {
      next: () => {
        const c = this.a + this.b
        this.a = this.b
        this.b = c
        return {
          value: this.b,
          done: false,
        }
      },
    }
  }
}

const fib = new Fib()

for (const n of fib) {
  console.log(n)

  if (n > 20) {
    break
  }
}
числа Фибоначчи

Что мы сделали?

Создали класс Fib с методом [Symbol.iterator], который возвращает итератор. Теперь по инстансу этого класса можно итерироваться в цикле, как по обычному массиву.

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

Можно было бы ограничить максимальное значение и внутри класса, достаточно вернуть из функции next объект с done=false.

И ещё один любопытный пример. Так как [Symbol.iterator] - просто метод объекта, можно вызывать его вручную у других объектов. А учитывая, что итератор — просто объект, то в одном классе можно иметь разные методы для итерации по нему.

Напишем список дел, который в основном итераторе возвращает все дела, а в дополнительном методе незавершённые.

class TodoItem {
  constructor(public text: string, public done = false) {}
}

class TodoList {
  constructor(private readonly items: TodoItem[]) {}

  [Symbol.iterator]() {
    return this.items[Symbol.iterator]()
  }

  workInProgress() {
    return this.items.filter(value => !value.done)[Symbol.iterator]()
  }
}

const todoList = new TodoList([
  new TodoItem('task1'),
  new TodoItem('task2', true),
  new TodoItem('task3'),
])

console.log('All:')
for (const item of todoList) {
  console.log(item.text)
}

console.log('Work in progress:')
for (const item of todoList.workInProgress()) {
  console.log(item.text)
}
результат работы программы с несколькими итераторами

В классе TodoList мы определили метод для итерации, который просто возвращает итератор внутреннего массива, что бы можно было использовать цикл прямо по объекту, а не по его свойству. Дополнительно создали метод workInProgress, в котором мы фильтруем массив items, возвращая итератор полученного массива, в котором только незавершённые задачи.

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

🚀  Если узнал из статьи что-то полезное, ставь лайк и подписывайся на наш канал в Телеграм или группу ВК. Обсудить статью можно в нашем уютном чатике 😏

© 2019 - 2022, {wcademy}