Загрузка…
Загрузка…
for...in vs for...of в JavaScriptЭто два разных цикла с совершенно разными назначениями. Их часто путают, и это классический вопрос на собеседовании.
for...in | for...of | |
|---|---|---|
| Что перебирает | ключи (индексы/свойства) | значения |
| Для чего | объекты {} | массивы, строки, Map, Set, любые итерируемые |
| Возвращает | строку (имя свойства) | сам элемент |
| Порядок | не гарантирован | гарантирован (порядок вставки) |
| Итерируемые | ❌ не требует | ✅ требует (Symbol.iterator) |
| Свои свойства | ✅ включает унаследованные | ❌ только элементы |
for...in — перебирает ключиconst obj = { a: 1, b: 2, c: 3 };
for (const key in obj) {
console.log(key); // 'a', 'b', 'c'
console.log(obj[key]); // 1, 2, 3
}key — это строка (имя свойства). Чтобы получить значение, нужно obj[key].
for...in даёт индексыconst arr = ['x', 'y', 'z'];
for (const i in arr) {
console.log(i); // '0', '1', '2' ← строки, не числа!
console.log(arr[i]); // 'x', 'y', 'z'
}for...of — перебирает значенияconst arr = ['x', 'y', 'z'];
for (const value of arr) {
console.log(value); // 'x', 'y', 'z' ← сразу значение
}С объектом {} не работает:
const obj = { a: 1, b: 2 };
for (const x of obj) { } // ❌ TypeError: obj is not iterableПотому что объект не итерируемый — у него нет Symbol.iterator.
for...in захватывает унаследованные свойстваconst parent = { inherited: 'from proto' };
const child = Object.create(parent);
child.own = 'own property';
for (const key in child) {
console.log(key); // 'own', 'inherited' ← оба!
}
for (const key of Object.keys(child)) {
console.log(key); // 'own' ← только свои
}Это главная ловушка for...in: он идёт по всей цепочке прототипов. Для объектов обычно используют Object.keys() + for...of или Object.entries().
for...in не гарантирует порядокconst obj = { b: 1, a: 2, 10: 3, 2: 4 };
for (const k in obj) console.log(k);
// '2', '10', 'b', 'a' ← числовые ключи идут первыми и отсортированыСпецификация: числовые ключи идут в возрастающем порядке, строковые — в порядке добавления. Это не «порядок вставки», как у Map/Set.
for...of всегда идёт в порядке итератора (обычно — порядок вставки/индексов).
for...of работает с любыми итерируемыми// Строка
for (const ch of 'hi') console.log(ch); // 'h', 'i'
// Map
const map = new Map([['a', 1], ['b', 2]]);
for (const [key, value] of map) {
console.log(key, value); // 'a' 1, 'b' 2
}
// Set
for (const v of new Set([1, 2, 3])) console.log(v); // 1, 2, 3
// Генератор
function* gen() { yield 1; yield 2; }
for (const x of gen()) console.log(x); // 1, 2for...in со всем этим даст мусор (индексы, имена методов) или ничего.
for...in использует внутренний механизм [[OwnPropertyKeys]] + обход прототипов → возвращает имена свойств (строки).
for...of вызывает у объекта метод [Symbol.iterator]() и по очереди дёргает .next(), пока done !== true.
// Упрощённо, for...of для массива эквивалентен:
const iterator = arr[Symbol.iterator]();
let res = iterator.next();
while (!res.done) {
const value = res.value;
// тело цикла
res = iterator.next();
}Поэтому for...of работает только с теми, у кого есть Symbol.iterator. Можно добавить его своему объекту:
const obj = {
a: 1,
b: 2,
[Symbol.iterator]() {
const values = Object.values(this);
let i = 0;
return {
next: () => i < values.length
? { value: values[i++], done: false }
: { value: undefined, done: true },
};
},
};
for (const v of obj) console.log(v); // 1, 2 ← теперь работает| Критерий | for...in | for...of |
|---|---|---|
| Что возвращает | ключ (строку) | значение |
Работает с {} | ✅ | ❌ (без кастома) |
| Работает с массивами | ⚠️ даёт индексы | ✅ даёт значения |
| Работает со строками | ⚠️ даёт индексы | ✅ даёт символы |
Работает с Map/Set | ❌ | ✅ |
| Обходит прототип | ✅ (плохо) | ❌ |
| Гарантирует порядок | ❌ | ✅ |
Можно break/continue | ✅ | ✅ |
Требует Symbol.iterator | ❌ | ✅ |
for...in — почти никогдаТолько если нужно явно обойти все свойства, включая унаследованные (редкий кейс, обычно для отладки). В 99% случаев вместо него:
for (const key of Object.keys(obj)) { }
for (const [key, value] of Object.entries(obj)) { }for...of — для всего остальногоfor (const item of array) { }
for (const ch of string) { }
for (const [k, v] of map) { }
for (const v of set) { }Object.entriesconst obj = { a: 1, b: 2 };
for (const [key, value] of Object.entries(obj)) {
console.log(key, value); // 'a' 1, 'b' 2
}Это идиоматичный способ в современном JS.
Array.prototype.myMethod = function() {};
const arr = [10, 20, 30];
// ❌ плохо
for (const i in arr) {
console.log(i, arr[i]); // 0 10, 1 20, 2 30, myMethod [Function]
}
// ✅ хорошо
for (const v of arr) {
console.log(v); // 10, 20, 30
}for...in зацепит myMethod, потому что мы добавили его в прототип. for...of — нет.
for...in — идёт по ключам объекта, включая унаследованные, порядок не гарантирован. Для объектов в современном JS почти не используется.for...of — идёт по значениям итерируемого (массив, строка, Map, Set), порядок гарантирован. Это то, что ты хочешь в 95% случаев.Правило: увидел for...in — почти наверняка там баг. Хочешь обойти объект — используй Object.keys/values/entries + for...of.
