Привет всем. Меня зовут Владислав Хирса, я — Software Engineer в Grid Dynamics. В этой статье я расскажу вам много полезного об итерации в JavaScript.
Мы все используем итерацию для перебора массивов и объектов разных размеров и разных задач. Но знаем ли мы, как это работает? Как мы можем изменить поведение итерации над нашими данными и в каких случаях это может потребоваться?
Если вам интересны ответы на эти вопросы, то этот материал точно для вас.
Вспомним термины
Для лучшего понимания вспомним несколько значений, а именно:
- Перечисляемые свойства
enumerable properties— одно из трех свойств (configurable,enumerable,writable), имеющихся в объекте. Относительноenumerable, то она отвечает за то, можно ли вернуть свойство в циклеfor...in.
Например, так:
Object.prototype.getType = function() {
return this.type;
};
const object = {
language: 'JavaScript',
type: 'Lesson'
};
for (const key in object) {
console.log(key); // JavaScript, Lesson, getType;
};
- Итерированный объект и
terable object— это объект, построенный по определенному паттерну и имеющий типичное итерационное поведение. Он итерируется с помощьюspread syntax [...],for...ofиfor await...of. И в этой статье мы поговорим именно о нем. - Протокол итерации
iterator protocol— это протокол, с помощью которого мы можем создать собственные правила, по которым будет итерироваться наш объект. Если подробнее, то итерировать мы сможем такие типы данных, какstring,array,object.
Главные правила iterator protocol — это:
- У нас должен быть метод
next(). - Метод
next()должен обязательно возвращать объект типаiterable object. Он содержит ключиvalue, которые могут иметь любое значение, иdone, который может бытьtrueилиfalse. - Когда итерация завершилась, нужно обязательно вернуть объект
{ done: true }, ведь только после этого наша итерация закончится. - Если
doneне будет возвращен илиdoneбудет иметь какое-либо негативное значение, такое какundefined,nullи другие, то наша итерация будет бесконечной.
Создание итеративного поведения
Для создания итеративного поведения мы будем использовать следующие подходы:
Symbol.iteratorс методомnext();Symbol.iteratorс генератором;Symbol.asyncIteratorс генератором.
Так что по порядку, и начнем мы с Symbol.iterator с методом next().
var array = [10, 20, 30, 40, 50];
array[Symbol.iterator] = function () {
let i = 0;
return {
next() {
i++
return (i <= 5)
? { value: i, done }
: { done: true }
}
}
};
for (const el of array) {
console.log(el) // 1, 2, 3, 4, 5
};
Как это работает?
Сначала цикл for...of ищет в нашем объекте или среди тех, от которых он наследуется в prototype, есть ли у него Symbol.iterator. Если нет — то будет вызвана автоматическая ошибка, а если да — тогда он вызывает функцию, функция возвращает наш объект с методом next(), где и есть вся наша основная логика, и самое главное, что при каждой итерации цикла используется метод next(), который и возвращает значение по нашим условиям.
Symbol.iterator з генератором
С Symbol.iterator уже ознакомились, а как насчет генераторов?
Генератор, если коротко, то это способ создания того же iterable object для итерации по iterator protocol, то есть у него тоже есть метод next() и он итерируется циклом for...of, только синтаксис другой, и использовать его в некоторых случаях удобнее:
var it = {};
it[Symbol.iterator] = function* () {
yield 1;
yield 2;
yield 3;
};
it.type = 'Lesson';
console.log(it); // { type: "Lesson", Symbol(Symbol.iterator): * Symbol.iterator() }
console.log([...it]); // [1, 2, 3]
В этой части мы изменили поведение объекта и дали ему возможность быть итерированным. Без Symbol.iterator была бы ошибка такого типа — Uncaught TypeError: it is not iterable при попытке итерировать объект. Так же как мы видим, что к итерационным данным мы не имеем доступа напрямую, а с Symbol.iterator нет доступа к значениям объекта. Это очень удобно, и нет никаких мутаций данных.
Symbol.asyncIterator з генератором
Очень интересный метод обработки данных, с помощью которого мы можем асинхронно обрабатывать данные, разбивать тяжелые и длительные операции, которые не перегружают и не блокируют нашу систему. Так что хочу поделиться с вами, возможно, немного сложным, но полезным примером использования. Для удобства в этом примере мы используем типы.
Файл async-iterator.ts:
import { AsyncLimitIteratorParametersType } from './types';
export default class Iterator {
public static generateAsyncLimitIterator(
data: AsyncLimitIteratorParametersType
) {
const {
from,
to,
limit,
asyncFunction,
asyncFunctionParams
} = data;
return {
async *[Symbol.asyncIterator]() {
for (let now = from; now < to; now += limit) {
yield await asyncFunction(
asyncFunctionParams,
{ from, to, limit, now }
);
}
}
}
}
};
Файл types.d.ts:
export type AsyncLimitIteratorParametersType = {
from: number;
to: number;
limit: number;
asyncFunction: Function;
asyncFunctionParams: any;
}
export type IterationDataType = {
from: number;
to: number;
limit: number;
now: number;
};
Класс Iterator создан для асинхронной обработки (изменения) больших массивов данных.
Так что разберем, как его можно использовать и в чем его преимущества.
Класс Iterator
Итак, класс Iterator. У него есть один статический метод generateAsyncLimitIterator, к которому мы имеем доступ, не используя оператор new, метод generateAsyncLimitIterator принимает параметры:
from— из какого индекса изменять массив;to— по какой позиции по индексу изменять массив;limit— сколько элементов за одну итерацию захватить и пройти;asyncFunction— функция, в которой выполняются основные действия.
Например, запрос на базу данных для изменения объекта пользователей. asyncFunctionParams — параметры, принимаемые функцией asyncFunction.
Пример применения:
const iteratorParams = {
from: 0,
to: 1000,
limit: 10,
asyncFunction: changeUsers,
asyncFunctionParams: params
};
const iterateObj = Iterator.generateAsyncLimitIterator(iteratorParams);
for await (let value of iterateObj) {
//
}
После того, как мы создали асинхронный итератор, мы просто используем его в асинхронном цикле и не беспокоимся о переполненном стеке вызовов и о том, что заблокирован Event Loop, а просто используем надежный способ обработки данных.
На этом все! Надеюсь, что было полезно. Желаю успехов и продуктивного кодинга 😉
Этот материал – не редакционный, это – личное мнение его автора. Редакция может не разделять это мнение.

Сообщить об опечатке
Текст, который будет отправлен нашим редакторам: