Итераторы и генераторы в JavaScript



Книга Итераторы и генераторы в JavaScript



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



Итераторы и генераторы  —  это передовые концепции JavaScript, позволяющие эффективно и настраиваемо выполнять циклический переход по структурам данных. Они также предоставляют механизм для настройки поведения циклов for…of.



Разберемся, что именно представляют собой итераторы и генераторы.




Итераторы


В JavaScript функция-итератор  —  это уникальная функция, возвращающая объект-итератор. Объект-итератор  —  это объект, который через метод next() возвращает объект с двумя свойствами: value и done. Свойство value представляет собой следующее значение в последовательности, а свойство done указывает, достиг ли итератор конца последовательности.


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


Ниже приведен пример функции-итератора, выполняющей итерацию по массиву:


function Iterator(array) {
let nextIndex = 0;
return {
next: function () {
if (nextIndex < array.length) {
return {
value: array[nextIndex++],
done: false,
};
} else {
return {
value: undefined,
done: true,
};
}
},
};
}

const array = [1, 2, 3, 4, 5];
const arrayValue = Iterator(array);

console.log(arrayValue.next()); // { value: 1, done: false }
console.log(arrayValue.next()); // { value: 2, done: false }
console.log(arrayValue.next()); // { value: 3, done: false }
console.log(arrayValue.next()); // { value: 4, done: false }
console.log(arrayValue.next()); // { value: 5, done: false }
console.log(arrayValue.next()); // { value: undefined, done: true }

В приведенном выше коде определена функция Iterator, которая принимает в качестве аргумента массив и возвращает объект итератора. Объект итератора через метод next возвращает следующий элемент массива и обновляет внутреннюю переменную nextIndex для отслеживания индекса массива.


Метод next проверяет, меньше ли nextIndex длины массива. Если это так, то метод возвращает объект со значением массива в позиции nextIndex и устанавливает свойство done в false. После этого переменная nextIndex увеличивается на единицу. Если nextIndex больше или равно длине массива, то метод next устанавливает свойство done в true.


Далее в коде определяется массив чисел [1, 2, 3, 4, 5] и с помощью функции Iterator из него создается объект-итератор. Переменная arrayValue присваивается объекту-итератору.


Затем код многократно вызывает метод next на объекте-итераторе arrayValue, записывая возвращаемые объекты в консоль. При каждом вызове метода next возвращается объект, содержащий либо значение следующего элемента массива, либо свойство done, равное true, что свидетельствует о том, что в массиве больше нет элементов.


При первых нескольких вызовах next будут записаны значения [1, 2, 3, 4, 5], а при последующих вызовах  —  объекты со свойством done, равным true, и значением undefined.


Но можно же напрямую использовать функцию Symbol.iterator для итерации по массиву?


Посмотрим пример:


const array = [1, 2, 3, 4, 5];
const iterator = array[Symbol.iterator]();

console.log(iterator.next()); // { value: 1, done: false }
console.log(iterator.next()); // { value: 2, done: false }
console.log(iterator.next()); // { value: 3, done: false }
console.log(iterator.next()); // { value: 4, done: false }
console.log(iterator.next()); // { value: 5, done: false }
console.log(iterator.next()); // { value: undefined, done: true }

Как видим, Symbol.iterator  —  это функция, возвращающая следующую функцию, которая является той же самой, что и функция, созданная ранее.




Генераторы


Функция-генератор  —  это особый тип функции, который позволяет управлять ходом выполнения, выдавая значения по одному за раз, а не возвращая их все сразу. Когда функция-генератор вызывается, она не выполняется немедленно, а возвращает объект-генератор, который можно использовать для управления выполнением функции.


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


Функции-генераторы удобны при создании итераторов и написании асинхронного кода с использованием синтаксиса async/await. Они позволяют писать код, который выглядит как синхронный, но выполняется асинхронно в фоновом режиме.


Функции-генераторы объявляются с использованием синтаксиса function*, который аналогичен синтаксису обычных функций, но после ключевого слова function ставится звездочка (*).


Приведем пример простой функции-генератора, которая выдает числа 1, 2 и 3:


function* myGenerator() {
yield 1;
yield 2;
yield 3;
}

const generator = myGenerator();

console.log(generator.next()); // { value: 1, done: false }
console.log(generator.next()); // { value: 2, done: false }
console.log(generator.next()); // { value: 3, done: false }
console.log(generator.next()); // { value: undefined, done: true }

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


Каждый вызов next() возобновляет выполнение функции-генератора с той точки, на которой она была приостановлена последним оператором yield. Когда у функции-генератора больше нет значений для выдачи, она возвращает { value: undefined, done: true }.


Поясним это на примере ряда Фибоначчи.


function* fibonacciGenerator() {
let current = 0;
let next = 1;

while (true) {
yield current;
[current, next] = [next, current + next];
}
}

// Создание экземпляра генератора Фибоначчи
const fibonacci = fibonacciGenerator();

// Генерация первых 10 чисел Фибоначчи
for (let i = 0; i < 10; i++) {
console.log(fibonacci.next().value);
}

// Вывод
// 0 1 1 2 3 5 8 13 21 34

В этом примере определяем функцию-генератор fibonacciGenerator(). С помощью ключевого слова yield она возвращает текущее число Фибоначчи и продолжает выполнение с того места, на котором остановилась.


Внутри функции-генератора поддерживаем две переменные: current и next. Начинаем с того, что текущая переменная current будет равна 0, а next  —  1, представляя собой первые два числа Фибоначчи.


Генератор входит в бесконечный цикл с помощью функции while(true). На каждой итерации он выдает текущее число Фибоначчи с помощью yield current, а затем вычисляет следующее число Фибоначчи путем сложения current и next. Наконец, происходит обновление значений current и next с помощью деструктурирующего присваивания: [current, next] = [next, current + next].


Для использования генератора создадим его экземпляр с помощью const fibonacci = fibonacciGenerator(). Затем можно вызвать fibonacci.next().value для получения следующего числа Фибоначчи в последовательности.


Здесь генерируем первые 10 чисел Фибоначчи, вызывая в цикле fibonacci.next().value и выводя результат на консоль.




Преимущества итераторов и генераторов


Из множества преимуществ итераторов и генераторов выделим 5 ключевых.


1. Итерация по наборам данных



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

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


2. Ленивая оценка



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

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


3. Многократное использование кода



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

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


4. Асинхронная итерация



  • Функции-генераторы могут быть использованы для реализации асинхронной итерации, что позволяет более рационально работать с асинхронными источниками данных, такими как API и потоки.

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


5. Повышение производительности



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

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


Заключение


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


Подобно тому, как хорошо организованное пространство кухни помогает повару готовить вкусные блюда, функции Iterator и Generator являются необходимыми инструментами для современных JavaScript-разработчиков, позволяющими эффективно справляться с требованиями обработки данных.



57   0  
    Ничего не найдено.

Добавить ответ:
Отменить.