Запропонувати правкуПокращити цю статтюДопрацюйте відповідь до «For...in проти for...of в JavaScript». Ваші зміни проходять модерацію перед публікацією.Потрібне підтвердженняКонтентЩо ви змінюєте🇺🇸EN🇺🇦UAПереглядЗаголовок (UA)Коротка відповідь (UA)**`for...in`** перебирає перелічувані ключі властивостей об'єкта (як рядки). **`for...of`** перебирає значення з будь-якого ітерованого об'єкта. ```javascript const obj = { a: 1 }; const arr = [1, 2]; for (const key in obj) { console.log(key); } // "a" (рядковий ключ) for (const val of arr) { console.log(val); } // 1, 2 (значення) ``` **Правило:** `for...in` для об'єктів, `for...of` для масивів, рядків, Map та Set. Ніколи не використовуй `for...in` на масивах - отримаєш рядкові індекси і властивості з прототипу.Показується над повною відповіддю для швидкого нагадування.Відповідь (UA)Зображення**`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...in | for...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-елементів у циклі або обробки будь-якої впорядкованої колекції.Для рев’юераПримітка для модератора (необов’язково)Бачить лише модератор. Прискорює рев’ю.