Ты пишешь класс PaginatedList — список товаров с пагинацией. Хочешь, чтобы код мог написать for (const product of list) — точно так же как с обычным массивом. Или ваша доменная модель DateRange должна поддерживать деструктуризацию и spread-оператор.
Итерируемый протокол позволяет любому объекту работать в for...of, деструктуризации, spread и Array.from.
// Строки
for (const char of 'Привет') console.log(char) // П, р, и, в, е, т
// Map
const prices = new Map([['apple', 120], ['banana', 80]])
for (const [product, price] of prices) {
console.log(product, price)
}
// Set
for (const id of new Set([1, 2, 2, 3])) {
console.log(id) // 1, 2, 3 — дубли убраны
}Чтобы объект стал итерируемым, нужно добавить метод [Symbol.iterator](), возвращающий итератор — объект с методом next():
const range = {
from: 1,
to: 5,
[Symbol.iterator]() {
let current = this.from
const last = this.to
return {
next() {
if (current <= last) {
return { value: current++, done: false }
}
return { value: undefined, done: true }
}
}
}
}
for (const n of range) console.log(n) // 1, 2, 3, 4, 5
console.log([...range]) // [1, 2, 3, 4, 5]
const [first, , third] = range // first=1, third=3// Оба принимают любой итерируемый объект:
Array.from('hello') // ['h', 'e', 'l', 'l', 'o']
Array.from(new Set([1, 2, 3])) // [1, 2, 3]
[...new Map([['a', 1]])] // [['a', 1]]
Array.from(range) // [1, 2, 3, 4, 5]
// Array.from с трансформацией:
Array.from({ length: 5 }, (_, i) => i * 2) // [0, 2, 4, 6, 8]class Range {
constructor(start, end, step = 1) {
this.start = start
this.end = end
this.step = step
}
[Symbol.iterator]() {
let current = this.start
const { end, step } = this
return {
next() {
if (current <= end) {
const value = current
current += step
return { value, done: false }
}
return { value: undefined, done: true }
}
}
}
}
const evens = new Range(0, 10, 2)
console.log([...evens]) // [0, 2, 4, 6, 8, 10]Два разных протокола, часто путают:
| | Iterable | Array-like |
|---|---|---|
| Признак | Symbol.iterator | length + числовые ключи |
| Работает в for...of | Да | Нет |
| Примеры | Array, Map, Set, строки | arguments, DOM NodeList |
Array.from принимает оба вида.
1. Итерируют обычный объект через for...of:
// Сломано — обычный объект не итерируемый:
const obj = { a: 1, b: 2 }
for (const v of obj) { ... } // TypeError: obj is not iterable
// Исправлено — итерируй по значениям:
for (const v of Object.values(obj)) { ... } // 1, 22. Забывают что next() должен возвращать { value, done }:
// Сломано — неверный формат:
return {
next() {
return current // просто число — ошибка!
}
}
// Исправлено:
return {
next() {
return { value: current++, done: false }
}
}3. Не возвращают { value: undefined, done: true } при завершении:
// Сломано — бесконечный итератор:
next() {
return { value: current++ } // нет done: true — никогда не остановится!
}
// Исправлено:
next() {
if (current > end) return { value: undefined, done: true }
return { value: current++, done: false }
}function*) — автоматически реализуют протокол итератора, более короткая записьfor await (const chunk of stream) — асинхронная итерацияnext()Класс Range для диапазонов и итерируемый пагинатор
// Итерируемый класс Range
class Range {
constructor(start, end, step = 1) {
this.start = start
this.end = end
this.step = step
}
[Symbol.iterator]() {
let current = this.start
const { end, step } = this
return {
next() {
if (current <= end) {
const value = current
current += step
return { value, done: false }
}
return { value: undefined, done: true }
}
}
}
toArray() { return [...this] }
size() { return Math.floor((this.end - this.start) / this.step) + 1 }
}
// Использование как обычного массива
const r = new Range(1, 10)
for (const n of r) process.stdout.write(n + ' ') // 1 2 3 4 5 6 7 8 9 10
console.log()
console.log([...new Range(0, 20, 5)]) // [0, 5, 10, 15, 20]
const [first, second, third] = new Range(10, 50, 10)
console.log(first, second, third) // 10 20 30
// Итерируемый пагинатор — ленивая загрузка
class Paginator {
constructor(items, pageSize = 3) {
this.items = items
this.pageSize = pageSize
}
[Symbol.iterator]() {
let offset = 0
const { items, pageSize } = this
return {
next() {
if (offset >= items.length) {
return { value: undefined, done: true }
}
const page = items.slice(offset, offset + pageSize)
offset += pageSize
return { value: page, done: false }
}
}
}
}
const products = ['MacBook', 'iPhone', 'AirPods', 'iPad', 'Watch', 'HomePod', 'TV']
const paginator = new Paginator(products, 3)
console.log('\nСтраницы товаров:')
let page = 1
for (const pageItems of paginator) {
console.log(`Страница ${page++}:`, pageItems)
}
// Страница 1: ['MacBook', 'iPhone', 'AirPods']
// Страница 2: ['iPad', 'Watch', 'HomePod']
// Страница 3: ['TV']Ты пишешь класс PaginatedList — список товаров с пагинацией. Хочешь, чтобы код мог написать for (const product of list) — точно так же как с обычным массивом. Или ваша доменная модель DateRange должна поддерживать деструктуризацию и spread-оператор.
Итерируемый протокол позволяет любому объекту работать в for...of, деструктуризации, spread и Array.from.
// Строки
for (const char of 'Привет') console.log(char) // П, р, и, в, е, т
// Map
const prices = new Map([['apple', 120], ['banana', 80]])
for (const [product, price] of prices) {
console.log(product, price)
}
// Set
for (const id of new Set([1, 2, 2, 3])) {
console.log(id) // 1, 2, 3 — дубли убраны
}Чтобы объект стал итерируемым, нужно добавить метод [Symbol.iterator](), возвращающий итератор — объект с методом next():
const range = {
from: 1,
to: 5,
[Symbol.iterator]() {
let current = this.from
const last = this.to
return {
next() {
if (current <= last) {
return { value: current++, done: false }
}
return { value: undefined, done: true }
}
}
}
}
for (const n of range) console.log(n) // 1, 2, 3, 4, 5
console.log([...range]) // [1, 2, 3, 4, 5]
const [first, , third] = range // first=1, third=3// Оба принимают любой итерируемый объект:
Array.from('hello') // ['h', 'e', 'l', 'l', 'o']
Array.from(new Set([1, 2, 3])) // [1, 2, 3]
[...new Map([['a', 1]])] // [['a', 1]]
Array.from(range) // [1, 2, 3, 4, 5]
// Array.from с трансформацией:
Array.from({ length: 5 }, (_, i) => i * 2) // [0, 2, 4, 6, 8]class Range {
constructor(start, end, step = 1) {
this.start = start
this.end = end
this.step = step
}
[Symbol.iterator]() {
let current = this.start
const { end, step } = this
return {
next() {
if (current <= end) {
const value = current
current += step
return { value, done: false }
}
return { value: undefined, done: true }
}
}
}
}
const evens = new Range(0, 10, 2)
console.log([...evens]) // [0, 2, 4, 6, 8, 10]Два разных протокола, часто путают:
| | Iterable | Array-like |
|---|---|---|
| Признак | Symbol.iterator | length + числовые ключи |
| Работает в for...of | Да | Нет |
| Примеры | Array, Map, Set, строки | arguments, DOM NodeList |
Array.from принимает оба вида.
1. Итерируют обычный объект через for...of:
// Сломано — обычный объект не итерируемый:
const obj = { a: 1, b: 2 }
for (const v of obj) { ... } // TypeError: obj is not iterable
// Исправлено — итерируй по значениям:
for (const v of Object.values(obj)) { ... } // 1, 22. Забывают что next() должен возвращать { value, done }:
// Сломано — неверный формат:
return {
next() {
return current // просто число — ошибка!
}
}
// Исправлено:
return {
next() {
return { value: current++, done: false }
}
}3. Не возвращают { value: undefined, done: true } при завершении:
// Сломано — бесконечный итератор:
next() {
return { value: current++ } // нет done: true — никогда не остановится!
}
// Исправлено:
next() {
if (current > end) return { value: undefined, done: true }
return { value: current++, done: false }
}function*) — автоматически реализуют протокол итератора, более короткая записьfor await (const chunk of stream) — асинхронная итерацияnext()Класс Range для диапазонов и итерируемый пагинатор
// Итерируемый класс Range
class Range {
constructor(start, end, step = 1) {
this.start = start
this.end = end
this.step = step
}
[Symbol.iterator]() {
let current = this.start
const { end, step } = this
return {
next() {
if (current <= end) {
const value = current
current += step
return { value, done: false }
}
return { value: undefined, done: true }
}
}
}
toArray() { return [...this] }
size() { return Math.floor((this.end - this.start) / this.step) + 1 }
}
// Использование как обычного массива
const r = new Range(1, 10)
for (const n of r) process.stdout.write(n + ' ') // 1 2 3 4 5 6 7 8 9 10
console.log()
console.log([...new Range(0, 20, 5)]) // [0, 5, 10, 15, 20]
const [first, second, third] = new Range(10, 50, 10)
console.log(first, second, third) // 10 20 30
// Итерируемый пагинатор — ленивая загрузка
class Paginator {
constructor(items, pageSize = 3) {
this.items = items
this.pageSize = pageSize
}
[Symbol.iterator]() {
let offset = 0
const { items, pageSize } = this
return {
next() {
if (offset >= items.length) {
return { value: undefined, done: true }
}
const page = items.slice(offset, offset + pageSize)
offset += pageSize
return { value: page, done: false }
}
}
}
}
const products = ['MacBook', 'iPhone', 'AirPods', 'iPad', 'Watch', 'HomePod', 'TV']
const paginator = new Paginator(products, 3)
console.log('\nСтраницы товаров:')
let page = 1
for (const pageItems of paginator) {
console.log(`Страница ${page++}:`, pageItems)
}
// Страница 1: ['MacBook', 'iPhone', 'AirPods']
// Страница 2: ['iPad', 'Watch', 'HomePod']
// Страница 3: ['TV']Ты разрабатываешь генератор временных слотов для системы бронирования (как Calendly). Создай итерируемый класс `TimeSlots`, который генерирует временные слоты для рабочего дня. Конструктор принимает: - `startHour` — начало рабочего дня (например, 9) - `endHour` — конец (например, 18) - `slotMinutes` — длительность слота в минутах (например, 30) Каждая итерация возвращает строку вида `"09:00"`, `"09:30"`, `"10:00"`, ... Слот `endHour:00` не включается.
Форматирование: String(hours).padStart(2, "0") + ":" + String(mins).padStart(2, "0"). currentMinutes начинается с startHour * 60, увеличивается на slotMinutes, заканчивается при >= endHour * 60.