← Назад к вопросам
Можно ли создать объект который умеет работать с for…of?
1.7 Middle🔥 141 комментариев
#JavaScript Core
Комментарии (1)
🐱
claude-haiku-4.5PrepBro AI3 апр. 2026 г.(ред.)
Ответ сгенерирован нейросетью и может содержать ошибки
Итерируемые объекты и for...of в JavaScript
Да, полностью можно создать объект, который работает с for...of. Для этого нужно реализовать протокол итерируемости (Iterable Protocol) через специальный метод Symbol.iterator.
1. Понимание итерируемости
// Встроенные итерируемые объекты (работают с for...of)
const array = [1, 2, 3];
for (const item of array) {
console.log(item); // 1, 2, 3
}
// Строки (итерируемые)
const str = "hello";
for (const char of str) {
console.log(char); // h, e, l, l, o
}
// Map (итерируемая)
const map = new Map([["a", 1], ["b", 2]]);
for (const [key, value] of map) {
console.log(key, value); // a 1, b 2
}
// Обычный объект НЕ итерируемый
const obj = { a: 1, b: 2 };
for (const item of obj) {
// TypeError: obj is not iterable
}
2. Реализация Symbol.iterator для пользовательского объекта
// Простой класс с итерируемостью
class CountUp {
constructor(max) {
this.max = max;
}
// Реализуем Iterable Protocol
[Symbol.iterator]() {
let count = 0;
const max = this.max;
return {
// Этот объект должен иметь метод next()
next() {
count++;
if (count <= max) {
return { value: count, done: false };
} else {
return { done: true };
}
}
};
}
}
// Использование
const counter = new CountUp(3);
for (const num of counter) {
console.log(num); // 1, 2, 3
}
// Также работает со spread оператором
const array = [...counter]; // [1, 2, 3]
// И с деструктуризацией
const [first, second, third] = counter; // first=1, second=2, third=3
3. Более сложный пример: итерируемый класс
// Класс для хранения пользователей с итерируемостью
class UserCollection {
private users: Array<{ id: number; name: string }> = [];
addUser(name: string) {
this.users.push({
id: this.users.length + 1,
name
});
}
// Реализуем итерируемость
[Symbol.iterator]() {
let index = 0;
const users = this.users;
return {
next() {
if (index < users.length) {
return {
value: users[index++],
done: false
};
}
return { done: true };
}
};
}
}
// Использование
const users = new UserCollection();
users.addUser("Alice");
users.addUser("Bob");
users.addUser("Charlie");
for (const user of users) {
console.log(user.name); // Alice, Bob, Charlie
}
// Работает с методами, которые ожидают итерируемые объекты
const userArray = Array.from(users); // Преобразует в массив
const names = [...users].map(u => u.name); // Spread оператор
4. Использование Generator для упрощения
// Generator функция - самый простой способ создать итератор
class Range {
constructor(start, end) {
this.start = start;
this.end = end;
}
*[Symbol.iterator]() {
// function* = generator function
// yield = вернуть значение и паузировать
for (let i = this.start; i <= this.end; i++) {
yield i;
}
}
}
// Использование
const range = new Range(1, 5);
for (const num of range) {
console.log(num); // 1, 2, 3, 4, 5
}
// Generator намного проще чем вручную создавать { next() }
5. Итерируемые паттерны
// ПАТТЕРН 1: Простой счётчик (как выше)
// ПАТТЕРН 2: Фильтрованная коллекция
class FilteredList {
constructor(items, predicate) {
this.items = items;
this.predicate = predicate;
}
*[Symbol.iterator]() {
for (const item of this.items) {
if (this.predicate(item)) {
yield item;
}
}
}
}
const numbers = [1, 2, 3, 4, 5];
const evens = new FilteredList(numbers, n => n % 2 === 0);
for (const num of evens) {
console.log(num); // 2, 4
}
// ПАТТЕРН 3: Трансформирующая коллекция
class MappedList {
constructor(items, transform) {
this.items = items;
this.transform = transform;
}
*[Symbol.iterator]() {
for (const item of this.items) {
yield this.transform(item);
}
}
}
const squared = new MappedList([1, 2, 3], x => x * x);
for (const num of squared) {
console.log(num); // 1, 4, 9
}
// ПАТТЕРН 4: Бесконечная последовательность
class FibonacciSequence {
*[Symbol.iterator]() {
let a = 0, b = 1;
while (true) {
yield a;
[a, b] = [b, a + b];
}
}
}
const fib = new FibonacciSequence();
let count = 0;
for (const num of fib) {
console.log(num);
if (++count >= 10) break; // Нужно остановиться вручную
}
6. Практический пример: API ответы
// Класс для работы с API, который поддерживает пагинацию
class PaginatedAPI {
private baseURL: string;
private pageSize: number;
constructor(baseURL: string, pageSize: number = 10) {
this.baseURL = baseURL;
this.pageSize = pageSize;
}
// Работает с for...of
async *[Symbol.asyncIterator]() {
let page = 1;
while (true) {
const response = await fetch(
`${this.baseURL}?page=${page}&limit=${this.pageSize}`
);
const data = await response.json();
if (data.items.length === 0) break; // Конец данных
for (const item of data.items) {
yield item; // yield из async generator
}
if (!data.hasNextPage) break;
page++;
}
}
}
// Использование с async for...of
async function fetchAll() {
const api = new PaginatedAPI("https://api.example.com/items");
for await (const item of api) {
// await нужен для async iterator
console.log(item);
}
}
7. Сравнение способов итерации
const data = { a: 1, b: 2, c: 3 };
// for...in (для обычных объектов) - НЕ рекомендуется
for (const key in data) {
console.log(key); // a, b, c (ключи)
console.log(data[key]); // 1, 2, 3
}
// Object.keys/values/entries (для обычных объектов)
for (const key of Object.keys(data)) {
console.log(key); // a, b, c
}
for (const value of Object.values(data)) {
console.log(value); // 1, 2, 3
}
for (const [key, value] of Object.entries(data)) {
console.log(key, value); // a 1, b 2, c 3
}
// for...of (для итерируемых объектов)
class IterableObject {
constructor(data) {
this.data = data;
}
*[Symbol.iterator]() {
for (const [key, value] of Object.entries(this.data)) {
yield { key, value };
}
}
}
const iterable = new IterableObject(data);
for (const item of iterable) {
console.log(item.key, item.value); // a 1, b 2, c 3
}
8. Проверка итерируемости
// Проверить, является ли объект итерируемым
function isIterable(obj) {
if (obj == null) return false;
return typeof obj[Symbol.iterator] === "function";
}
console.log(isIterable([1, 2, 3])); // true (массив)
console.log(isIterable("hello")); // true (строка)
console.log(isIterable(new Map())); // true (map)
console.log(isIterable(new Set())); // true (set)
console.log(isIterable({ a: 1 })); // false (обычный объект)
// Пользовательский объект с iterator
class MyIterable {
*[Symbol.iterator]() {
yield 1;
}
}
console.log(isIterable(new MyIterable())); // true
// Безопасно использовать
const obj = new MyIterable();
if (isIterable(obj)) {
for (const item of obj) {
console.log(item); // 1
}
}
Рекомендация
Для создания итерируемого объекта:
- Реализуй
Symbol.iterator— вернёт объект с методомnext() - Используй Generator (
*) — самый простой способ - Для асинхронных данных — реализуй
Symbol.asyncIteratorсasync * - Проверяй итерируемость —
Symbol.iterator in obj - Практические случаи: коллекции, пагинированные API, фильтры, трансформации