Skip to main content

For...in проти for...of в JavaScript

for...in перебирає перелічувані ключі властивостей (enumerable property keys) об'єкта у вигляді рядків; for...of перебирає значення з будь-якого ітерованого об'єкта: масиву, рядка, Map або Set.

Теорія

TL;DR

  • for...in читає підписи на поштових скриньках (ключі); for...of читає самі листи (значення)
  • for...in завжди дає рядкові ключі; for...of дає реальні значення будь-якого типу
  • for...in проходить по ланцюгу прототипів (prototype chain); for...of ні
  • Простий об'єкт? for...in. Масив, рядок, Map, Set? for...of
  • for...in на масивах технічно запускається, але призводить до реальних багів

Швидкий приклад

javascript
const obj = { a: 1, b: 2 }; const arr = [1, 2]; for (const key in obj) { console.log(key); // "a", "b" (рядкові ключі) } for (const val of arr) { console.log(val); // 1, 2 (самі значення) }

for...in дає ключі у вигляді рядків. for...of дає значення напряму. Ось і вся різниця.

Ключова відмінність

for...in створений для об'єктів. Він проходить по всіх перелічуваних властивостях, включно зі спадкованими через ланцюг прототипів, і завжди повертає ключ як рядок. for...of з'явився в ES2015 і працює з будь-чим, що реалізує протокол ітератора (iterator protocol), тобто має метод [Symbol.iterator]. Жодного обходу прототипів, жодного приведення типів. Просто значення по порядку.

Коли що використовувати

  • Перевіряєш структуру об'єкта (конфіг, req.headers): for...in
  • Перебираєш елементи масиву, символи рядка або записи з Map/Set: for...of
  • Потрібні і ключі, і значення об'єкта: for (const [k, v] of Object.entries(obj))
  • Хочеш уникнути проблем з прототипами: Object.keys(obj) з for...of безпечніше

Таблиця порівняння

Аспектfor...infor...of
ПеребираєПерелічувані ключі (рядки)Значення з ітерованих об'єктів
Працює зОб'єктами (включно з масивами)Масивами, рядками, Map, Set, генераторами
Ланцюг прототипівВключає спадковані властивостіІгнорується
Поведінка з масивамиІндекси як рядки ("0", "1")Прямі елементи, holes дають undefined
Типовий сценарійІнспекція ключів об'єктаОбхід колекцій

Як це працює

for...in викликає внутрішню операцію enumerate, обходить власні і прототипні властивості і повертає кожен ключ як рядок. Тому на масивах ти отримуєш "0" замість 0.

for...of викликає [Symbol.iterator]() на цільовому об'єкті, потім послідовно викликає .next() і читає об'єкти {value, done} поки done не стане true. Жодного обходу прототипів. Якщо у об'єкта немає [Symbol.iterator], одразу отримаєш TypeError.

Типові помилки

for...in на масивах

javascript
const arr = ["a", "b"]; for (const i in arr) { console.log(i, typeof i); // "0" "string", "1" "string" }

Індекси стають рядками, а не числами. Розріджені (sparse) масиви поводяться непередбачувано, а якщо якась бібліотека додала властивість до Array.prototype, вона теж потрапить у цикл. Використовуй for...of або звичайний for.

for...of на звичайному об'єкті

javascript
const obj = { a: 1, b: 2 }; for (const val of obj) { } // TypeError: obj is not iterable

Звичайні об'єкти не мають [Symbol.iterator]. Виправлення: for (const val of Object.values(obj)).

Забруднення прототипу і for...in

javascript
const arr = ["a", "b"]; Array.prototype.foo = "polluted"; for (const key in arr) { console.log(key); // "0", "1", "foo" }

В старіших кодових базах з legacy-бібліотеками це зустрічається частіше ніж хочеться. Додавай перевірку hasOwnProperty або взагалі не використовуй for...in на масивах.

Зміна об'єкта під час ітерації

javascript
const obj = { a: 1, b: 2 }; for (const k in obj) { delete obj.b; // Може пропустити "b" у V8 - поведінка не гарантована }

V8 робить знімок ключів до початку циклу. Видалення властивостей під час ітерації може призвести до пропусків. Збери ключі заздалегідь через Object.keys().

Де зустрічається в реальних проектах

  • Express: for...in по req.headers для логування всіх заголовків запиту
  • React: for...of при ручному обході масиву пропсів або побудові DOM-елементів
  • Node.js: for...of по process.argv.slice(2) для обробки аргументів CLI
  • Lodash: _.forIn обгортає for...in безпечно для роботи з об'єктами

Питання на співбесіді

Q: Що for...in повертає на масиві з "дірками" (holes)?
A: Пропускає їх, бо відповідної властивості просто немає. for...of дасть undefined на місці кожної дірки.

Q: Як зробити звичайний об'єкт ітерованим для for...of?
A: Оберни через Object.values(obj) або додай кастомний метод [Symbol.iterator] як генератор.

Q: Чому for...in повертає рядкові ключі на масивах?
A: Специфікація приводить всі ключі властивостей до рядків. Масиви зберігають елементи як іменовані властивості, тому індекс 0 стає "0".

Q: Порядок ітерації гарантований?
A: for...of на масивах і рядках дотримується порядку вставки. for...in порядок специфікацією не гарантований, хоча V8 зазвичай повертає власні ключі першими, потім прототипні.

Q: (Для досвідчених) Що відбувається у V8, якщо видалити властивість під час for...in?
A: V8 будує знімок ключів до початку циклу. Видалення після цього моменту не завжди виключає ключ з ітерації. Поведінка не визначена специфікацією, не покладайся на неї.

Приклади

Перевірка властивостей конфіг-об'єкта

javascript
const settings = { theme: "dark", lang: "en", debug: true }; for (const key in settings) { console.log(`${key}: ${settings[key]}`); } // theme: dark // lang: en // debug: true

for...in доречний для простих об'єктів, коли потрібні ключі. Такий патерн зустрічається у валідації конфігів, логувальних middleware і всюди, де треба пройтись по об'єкту невідомої форми.

Побудова списку з масиву

javascript
const todos = ["Купити молоко", "Написати код", "Поспати"]; const list = document.createElement("ul"); for (const todo of todos) { const item = document.createElement("li"); item.textContent = todo; list.appendChild(item); } // <ul><li>Купити молоко</li><li>Написати код</li><li>Поспати</li></ul>

for...of дає елементи напряму, без індексних змінних і без arr[i]. Це стандартний патерн для побудови DOM-елементів у циклі або обробки будь-якої впорядкованої колекції.

Коротка відповідь

Для співбесіди
Premium

Коротка відповідь допоможе вам впевнено відповідати на цю тему під час співбесіди.

Дочитали статтю?