Привет всем. Меня зовут Владислав Хирса, я — 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
уже ознакомились, а как насчет генераторов?
Генератор, если коротко, то это способ создания того же 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
нет доступа к значениям объекта. Это очень удобно, и нет никаких мутаций данных.
Очень интересный метод обработки данных, с помощью которого мы можем асинхронно обрабатывать данные, разбивать тяжелые и длительные операции, которые не перегружают и не блокируют нашу систему. Так что хочу поделиться с вами, возможно, немного сложным, но полезным примером использования. Для удобства в этом примере мы используем типы.
Файл 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
. У него есть один статический метод 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
, а просто используем надежный способ обработки данных.
На этом все! Надеюсь, что было полезно. Желаю успехов и продуктивного кодинга 😉
В благословенные офисные времена, когда не было большой войны и коронавируса, люди гораздо больше общались…
Вот две истории из собственного опыта, с тех пор, когда только начинал делать свою карьеру…
«Ты же программист». За свою жизнь я много раз слышал эту фразу. От всех. Кто…
Отличные новости! Если вы пропустили, GitHub Copilot — это уже не отдельный продукт, а набор…
Несколько месяцев назад мы с командой Promodo (агентство инвестировало в продукт более $100 000) запустили…
Пару дней назад прочитал сообщение о том, что хорошие курсы могут стать альтернативой классическому образованию.…