for...in проти for...of у масиві
Різниця між for...in і for...of у тому, що перший перебирає ключі, а другий значення: на масиві for...in дає індекси у вигляді рядків, а for...of дає самі елементи. Це одна з тих речей, які треба розуміти чітко, бо саме на масивах різниця найчастіше і стріляє.
Теорія
TL;DR
for...inперебирає ключі та індекси, тобто імена перелічуваних властивостей, і призначений для об'єктів.for...ofперебирає значення елементів ітерованих структур:Array,String,Set,Mapта інших.- На масиві
for...inповертає індекси як рядки ('0','1'), а не як числа. for...inпокаже і властивості, дописані до масиву вручну, і перелічувані властивості з прототипу.for...ofгарантує порядок елементів,for...inгарантій щодо порядку нечислових ключів не дає.- Для масивів беріть
for...of, для об'єктівfor...inабоObject.entries().
Швидкий приклад
const arr = ['a', 'b', 'c'];
for (const index in arr) {
console.log(index); // '0', '1', '2', рядки
}
for (const value of arr) {
console.log(value); // 'a', 'b', 'c', значення
}for...in перебирає ключі
const arr = ['a', 'b', 'c'];
for (const index in arr) {
console.log(index); // '0', '1', '2'
console.log(typeof index); // 'string'
}Що з цього випливає:
- цикл повертає рядкові індекси, а не числа, тому
index + 1дасть'01', а не1; - він створений для об'єктів, а не для масивів;
- він може захопити додаткові властивості, якщо їх дописати вручну або додати в прототип.
for...of перебирає значення
const arr = ['a', 'b', 'c'];
for (const value of arr) {
console.log(value); // 'a', 'b', 'c'
}Що з цього випливає:
- цикл працює з ітерованими структурами:
Array,String,Set,Map,arguments,NodeList; - він повертає значення елементів, а не ключі;
- це ідеальний вибір для масиву.
Якщо потрібні і індекс, і значення одночасно, беріть entries():
for (const [index, value] of arr.entries()) {
console.log(index, value); // 0 'a', 1 'b', 2 'c'
}Тут index вже число, а не рядок.
Різниця на практиці: зайві властивості
const arr = ['x', 'y', 'z'];
arr.custom = 'hello'; // додамо властивість вручну
for (const key in arr) {
console.log(key); // '0', '1', '2', 'custom', зайвий ключ
}
for (const value of arr) {
console.log(value); // 'x', 'y', 'z', тільки елементи
}for...in обходить усі перелічувані властивості об'єкта, включно з дописаними вручну і успадкованими з прототипу. for...of бере тільки елементи масиву.
Саме тому код на for...in часто «рятують» перевіркою, яка насправді потрібна лише через неправильно обраний цикл:
for (const key in arr) {
if (!Object.hasOwn(arr, key)) continue; // милиця, якої не треба з for...of
console.log(arr[key]);
}Ще одна деталь: for...in пропускає «дірки» у розрідженому масиві, а for...of проходить по них і віддає undefined.
const sparse = [1, , 3];
for (const key in sparse) console.log(key); // '0', '2'
for (const value of sparse) console.log(value); // 1, undefined, 3Рядки та інші ітеровані структури
for (const char of 'JS') {
console.log(char); // 'J', 'S'
}for...of чудово працює з рядками, Set і Map:
const set = new Set([1, 2, 2, 3]);
for (const value of set) console.log(value); // 1, 2, 3
const map = new Map([['a', 1], ['b', 2]]);
for (const [key, value] of map) console.log(key, value); // 'a' 1, 'b' 2for...in на рядку поверне індекси '0', '1', а на Set чи Map не дасть нічого корисного, бо їхні дані лежать не в перелічуваних властивостях.
Порівняльна таблиця
| Особливість | for...in | for...of |
|---|---|---|
| Перебирає | ключі (індекси, властивості) | значення |
| Працює з | об'єктами, масивами | ітерованими структурами (масив, рядок, Set, Map) |
| Повертає | рядки, імена властивостей | самі значення |
| Порядок обходу | для нечислових ключів непередбачуваний | зберігає порядок ітератора |
| Бачить дописані властивості | Так | Ні |
| Підходить для масивів | Ні | Так |
| Підходить для об'єктів | Так | Ні (без Object.entries()) |
Просте правило, яке варто запам'ятати:
for...inперебирає ключі і потрібен для об'єктів.for...ofперебирає значення і потрібен для масивів та ітерованих структур.
Типові помилки
- Брати
for...inдля масиву. Це найчастіша помилка: цикл віддасть рядкові індекси і зачепить зайві властивості. - Рахувати індекс числом. У
for...inіндекс це рядок, томуindex + 1дає конкатенацію'01'. ПотрібенNumber(index)абоarr.entries(). - Пробувати
for...ofна звичайному об'єкті. Простий об'єкт не ітерований, тому будеTypeError: obj is not iterable. ПотрібніObject.keys(),Object.values()абоObject.entries(). - Розраховувати на порядок у
for...in. Цілочислові ключі йдуть за зростанням, а решта в порядку додавання, тому змішаний об'єкт обходиться не так, як здається. - Забувати про прототип.
for...inпідіймається ланцюжком прототипів і покаже перелічувані властивості звідти теж. - Використовувати
breakуforEach. Це не проfor...inчиfor...of, але саме через це їх часто й обирають: уforEachнемаєbreakіcontinue, а в обох цих циклах вони працюють.
Коротка відповідь
Для співбесідиКоротка відповідь допоможе вам впевнено відповідати на цю тему під час співбесіди.