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

Как for…of понимает что у массива нужно перечислить только элементы по ключам?

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

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

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

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

Как 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 и даже пользовательскими объектами.

Как for…of понимает что у массива нужно перечислить только элементы по ключам? | PrepBro