Как for…of понимает что у массива нужно перечислить только элементы по ключам?
Комментарии (1)
Ответ сгенерирован нейросетью и может содержать ошибки
Как for...of понимает что у массива нужно перечислить только элементы по ключам
Это отличный вопрос о итераторах и протоколе итерации в JavaScript. Ответ связан с концепцией Symbol.iterator и итерируемых объектов.
Принцип работы for...of
for...of не просто проходит по свойствам объекта (как for...in). Он использует специальный протокол, основанный на итераторах.
// for...in — проходит по всем свойствам, включая индексы и добавленные свойства
const arr = [1, 2, 3];
arr.custom = 'value';
for (const key in arr) {
console.log(key); // 0, 1, 2, custom
}
// for...of — проходит ТОЛЬКО по значениям элементов массива
for (const value of arr) {
console.log(value); // 1, 2, 3
}
for...of знает, что нужно перечислить именно элементы массива, потому что массив реализует итерируемый протокол.
Symbol.iterator — ключ к пониманию
Каждый массив имеет скрытое свойство Symbol.iterator, которое возвращает итератор:
const arr = [1, 2, 3];
// Это то, что вызывает for...of
const iterator = arr[Symbol.iterator]();
// Итератор — это объект с методом next()
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 }
Когда вы пишете:
for (const value of arr) {
console.log(value);
}
В действительности это примерно эквивалентно:
const iterator = arr[Symbol.iterator]();
let result = iterator.next();
while (!result.done) {
const value = result.value;
console.log(value);
result = iterator.next();
}
Почему for...of не использует for...in логику
for...in использует совсем другой механизм:
// for...in перечисляет ВСЕ перечисляемые свойства
const obj = { a: 1, b: 2, c: 3 };
Object.defineProperty(obj, 'd', {
value: 4,
enumerable: false // не будет видна в for...in
});
for (const key in obj) {
console.log(key); // a, b, c (не d)
}
for...of сознательно игнорирует всё это:
const arr = [1, 2, 3];
arr.custom = 'value';
Object.defineProperty(arr, 'hidden', { value: 'hidden', enumerable: false });
// for...of проходит ТОЛЬКО по элементам через iterator
for (const value of arr) {
console.log(value); // 1, 2, 3
}
// custom и hidden игнорируются!
Создание собственного итерируемого объекта
Вы можете создать собственный объект, который работает с for...of:
const customIterable = {
data: ['a', 'b', 'c'],
// Реализуем итерируемый протокол
[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 (const item of customIterable) {
console.log(item); // a, b, c
}
Генераторы как упрощённый способ создания итераторов
Генератор-функция автоматически создаёт итератор:
function* makeIterator() {
yield 1;
yield 2;
yield 3;
}
for (const value of makeIterator()) {
console.log(value); // 1, 2, 3
}
yield — это синтаксический сахар для создания объекта с структурой { value, done }.
Какие объекты итерируемы
Объекты, которые имеют Symbol.iterator:
// Итерируемы
for (const x of [1, 2, 3]) {} // Array
for (const x of 'abc') {} // String
for (const x of new Set([1, 2])) {} // Set
for (const x of new Map([['a', 1]])) {} // Map
// НЕ итерируемы (используй Object.keys/entries)
const obj = { a: 1, b: 2 };
for (const x of obj) {} // TypeError!
for (const [k, v] of Object.entries(obj)) {} // OK
Практический пример с array-like объектом
// Array-like, но НЕ итерируемый
const arrayLike = {
0: 'a',
1: 'b',
2: 'c',
length: 3
};
// Это НЕ работает
for (const x of arrayLike) {} // TypeError!
// Работает
for (const x of Array.from(arrayLike)) {} // a, b, c
Array.from автоматически вызывает Symbol.iterator, если объект его поддерживает, иначе использует свойства с числовыми индексами.
Заключение
for...of работает с массивами не потому, что он анализирует структуру объекта. Он работает с итерируемым протоколом, который встроен в JavaScript. Массив имеет встроенный Symbol.iterator, который возвращает итератор, перечисляющий только индексированные элементы. Это позволяет for...of быть универсальным инструментом, работающим с любыми итерируемыми объектами — массивами, строками, Set, Map и даже пользовательскими объектами.