Створення користувацьких ітераторів та ітерабельних об’єктів у JavaScript

pic

Як старший фронтенд-розробник, ви, ймовірно, вже знайомі з механізмами ітерації JavaScript, такими як for...of, forEach та оператором розширення. Ці інструменти залежать від протоколу ітерації — основоположної концепції, що дозволяє об'єктам визначати власну поведінку при ітерації. Але що, якщо вам потрібно здійснити ітерацію по власній структурі даних особливим способом? Тут на допомогу приходять користувацькі ітератори та ітерабельні об'єкти. Давайте розглянемо, що таке ітератори та ітерабельні об'єкти, і як їх реалізувати за допомогою JavaScript та TypeScript.

Що таке ітерабельні об'єкти та ітератори?

Ітерабельний об'єкт — це об'єкт, що визначає свою поведінку при ітерації, зазвичай використовуючи метод Symbol.iterator. Цей метод повертає ітератор, який є об'єктом, відповідальним за поетапне отримання значень.

  • Протокол ітерації: Об'єкт є ітерабельним, якщо він має метод Symbol.iterator, який повертає ітератор.
  • Протокол ітератора: Ітератор — це об'єкт з методом next, який повертає об'єкт, що містить два властивості:
    • value: Поточне значення в послідовності.
    • done: Булеве значення, яке вказує, чи завершено ітерацію.

Чому створювати власні ітератори та ітерабельні об'єкти?

Вбудовані об'єкти JavaScript, такі як масиви, карти та множини, за замовчуванням є ітерабельними. Однак для користувацьких структур даних або специфічних вимог до ітерації, визначення власних ітерабельних об'єктів може забезпечити:

  1. Гнучкість: Індивідуальна логіка ітерації для вашої структури даних.
  2. Інкапсуляція: Зберігання логіки ітерації всередині самого об'єкта.
    3.
    ## 1. Покращення читаємості
  • Покращення читаємості коду: Спрощення коду шляхом абстрагування складної логіки ітерації.

Реалізація користувацького ітератора

Розпочнемо з простого прикладу: створення користувацького ітератора для діапазону чисел.

Приклад 1: Базовий користувацький ітератор

function createRangeIterator(start, end) {  
 let current = start;  
 return {  
 next() {  
 if (current <= end) {  
 return { value: current++, done: false };  
 }  
 return { value: undefined, done: true };  
 },  
 };  
}  

const range = createRangeIterator(1, 5);  
console.log(range.next()); // { value: 1, done: false }  
console.log(range.next()); // { value: 2, done: false }  
console.log(range.next()); // { value: 3, done: false }

Створення ітерабельного об'єкта

Щоб зробити об'єкт ітерабельним, ми реалізуємо метод Symbol.iterator, який має повертати ітератор.

Приклад 2: Додавання протоколу ітерації

function createRange(start, end) {  
 return {  
 [Symbol.iterator]() {  
 return createRangeIterator(start, end);  
 },  
 };  
}  

const range = createRange(1, 5);  
for (const num of range) {  
 console.log(num); // 1, 2, 3, 4, 5  
}

Використання генераторів для спрощення

Генератори спрощують створення ітераторів, автоматично обробляючи стан і метод next.

Приклад 3: Користувацький ітерабельний об'єкт з генераторами

function createRange(start, end) {  
 return {  
 *[Symbol.iterator]() {  
 for (let i = start; i <= end; i++) {  
 yield i;  
 }  
 },  
 };  
}  

const range = createRange(1, 5);  
console.log([...range]); // [1, 2, 3, 4, 5]

Користувацькі ітерабельні об'єкти в TypeScript

TypeScript покращує досвід розробника, забезпечуючи типову безпеку та автодоповнення для ітерабельних об'єктів та ітераторів.

Приклад 4: Ітерабельний об'єкт в TypeScript

type RangeIteratorResult = { value: number; done: boolean };  

function createRangeIterator(start: number, end: number): Iterator {  
 let current = start;  
 return {  
 next(): RangeIteratorResult {  
 if (current <= end) {  
 return { value: current++, done: false };  
 }  
 return { value: undefined as any, done: true };  
 },  
 };  
}  
function createRange(start: number, end: number): Iterable {  
 return {  
 [Symbol.iterator](): Iterator {  
 return createRangeIterator(start, end);  
 },  
 };  
}  
const range = createRange(1, 5);  
for (const num of range) {  
 console.log(num); // 1, 2, 3, 4, 5  
}

Складний випадок використання: Двосторонній ітератор

Створимо користувацький двосторонній ітератор для ітерації як вперед, так і назад.

Приклад 5: Двосторонній ітератор

function createBidirectionalRange(start: number, end: number): Iterable {  
 let current = start;  
 return {  
 [Symbol.iterator]() {  
 return {  
 next(): IteratorResult {  
 if (current <= end) {  
 return { value: current++, done: false };  
 }  
 return { value: undefined as any, done: true };  
 },  

 previous(): IteratorResult {  
 if (current > start) {  
 return { value: --current, done: false };  
 }  
 return { value: undefined as any, done: true };  
 },  
 };  
 },  
 };  
}

Висновок

Створення користувацьких ітераторів та ітерабельних об'єктів дозволяє вам визначити, як мають ітерацію ваші структури даних. Дотримуючись протоколів ітерації та ітераторів, ви можете створювати інтуїтивно зрозумілу, багаторазову та інкапсульовану логіку ітерації. Незалежно від того, чи працюєте ви з JavaScript або TypeScript, ці інструменти підвищують вашу здатність створювати надійні та підтримувані додатки. Використовуйте користувацькі ітерабельні об'єкти для спрощення складних сценаріїв ітерації та надайте безшовний досвід для майбутніх розробників, які будуть використовувати ваш код.

Перекладено з: Creating Custom Iterators and Iterables in JavaScript

Leave a Reply

Your email address will not be published. Required fields are marked *