← Назад к вопросам

Как итерировать не массив?

2.0 Middle🔥 121 комментариев
#JavaScript Core

Комментарии (1)

🐱
claude-haiku-4.5PrepBro AI3 апр. 2026 г.(ред.)

Ответ сгенерирован нейросетью и может содержать ошибки

Итерирование не-массивов: iterable и iterator протоколы

В JavaScript не только массивы поддерживают итерирование. Есть специальные протоколы для создания итерируемых объектов и итераторов, которые работают с циклами for...of и spread оператором.

Понятие: Iterator и Iterable

Iterable (итерируемый) — объект, который имеет метод Symbol.iterator, возвращающий итератор.

Iterator (итератор) — объект с методом next(), возвращающий { value, done }.

// Пример встроенного итератора
const arr = [1, 2, 3];
const iterator = arr[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: undefined, done: true }

Встроенные итерируемые объекты

Много встроенных объектов в JavaScript уже итерируемы:

// Строки
for (const char of 'hello') {
  console.log(char); // h, e, l, l, o
}

// Set
const set = new Set([1, 2, 3]);
for (const value of set) {
  console.log(value); // 1, 2, 3
}

// Map
const map = new Map([['a', 1], ['b', 2]]);
for (const [key, value] of map) {
  console.log(key, value); // a 1, b 2
}

// NodeList (DOM)
const elements = document.querySelectorAll('p');
for (const el of elements) {
  console.log(el);
}

// Arguments
function printArgs() {
  for (const arg of arguments) {
    console.log(arg);
  }
}

Создание своего итерируемого объекта

Добавляем метод Symbol.iterator к объекту:

// Собственный объект
const customObject = {
  data: [1, 2, 3],
  
  // Делаем объект итерируемым
  [Symbol.iterator]() {
    let index = 0;
    const data = this.data;
    
    return {
      next() {
        if (index < data.length) {
          return {
            value: data[index++],
            done: false
          };
        } else {
          return { done: true };
        }
      }
    };
  }
};

// Теперь можем использовать for...of
for (const value of customObject) {
  console.log(value); // 1, 2, 3
}

// И spread оператор
const array = [...customObject]; // [1, 2, 3]

Практический пример: Range (диапазон чисел)

class Range {
  constructor(start, end) {
    this.start = start;
    this.end = end;
  }
  
  [Symbol.iterator]() {
    let current = this.start;
    const end = this.end;
    
    return {
      next() {
        if (current <= end) {
          return {
            value: current++,
            done: false
          };
        }
        return { done: true };
      }
    };
  }
}

const range = new Range(1, 5);

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

const arr = [...range]; // [1, 2, 3, 4, 5]

Generators — синтаксис сахар для итераторов

Geneator функции упрощают создание итераторов. Используй function* и yield:

// Без generator (сложно)
function createIterator(data) {
  let index = 0;
  return {
    next() {
      if (index < data.length) {
        return { value: data[index++], done: false };
      }
      return { done: true };
    }
  };
}

// С generator (просто!)
function* createIterator(data) {
  for (const item of data) {
    yield item;
  }
}

for (const item of createIterator([1, 2, 3])) {
  console.log(item);
}

Итерируемый объект с Generator

class IterableCollection {
  constructor(data) {
    this.data = data;
  }
  
  // Делаем класс итерируемым
  *[Symbol.iterator]() {
    for (const item of this.data) {
      yield item;
    }
  }
}

const collection = new IterableCollection([10, 20, 30]);
for (const item of collection) {
  console.log(item); // 10, 20, 30
}

Бесконечный итератор

// Бесконечный счётчик
function* infiniteCounter() {
  let count = 0;
  while (true) {
    yield count++;
  }
}

const counter = infiniteCounter();
console.log(counter.next().value); // 0
console.log(counter.next().value); // 1
console.log(counter.next().value); // 2

// С break для остановки
for (const num of infiniteCounter()) {
  if (num > 5) break;
  console.log(num); // 0, 1, 2, 3, 4, 5
}

Два-стороннее общение: Generator с send()

function* dialogue() {
  const greeting = yield 'Привет! Как дела?';
  const response = yield `Ты сказал: ${greeting}`;
  return `Спасибо за ответ: ${response}`;
}

const gen = dialogue();

console.log(gen.next().value); // 'Привет! Как дела?'
console.log(gen.next('Хорошо').value); // 'Ты сказал: Хорошо'
console.log(gen.next('Пока').value); // 'Спасибо за ответ: Пока'

Пример: Async iterator (загрузка данных)

class DataLoader {
  constructor(api) {
    this.api = api;
  }
  
  async *[Symbol.asyncIterator]() {
    let page = 1;
    let hasMore = true;
    
    while (hasMore) {
      const response = await fetch(`${this.api}?page=${page}`);
      const data = await response.json();
      
      for (const item of data.items) {
        yield item;
      }
      
      hasMore = data.hasMore;
      page++;
    }
  }
}

const loader = new DataLoader('https://api.example.com/items');

for await (const item of loader) {
  console.log(item); // Загружает данные по мере итерирования
}

Сравнение способов итерирования

Mетод              | Использование        | Особенности
------------------
for...of           | for (const x of obj)  | Требует Symbol.iterator
Object.keys()      | for (const k of keys) | Только собственные ключи
Object.entries()   | for (const [k,v]...) | Пары ключ-значение
forEach()          | obj.forEach(item)    | Callback функция
while + iterator   | while (!it.done)      | Полный контроль
Generator          | yield в function*     | Простота реализации
async iterator     | for await (x of obj)  | Асинхронная работа

Вывод: используй Symbol.iterator для создания итерируемых объектов, Generator функции для упрощения кода, и for...of для чистого итерирования.