谈谈你对 for...in / for...of 的理解?分别适用于什么场景?
面试速答(30 秒版 TL;DR)
for...in:遍历对象的可枚举属性名(key),key 是字符串;会包含原型链上可枚举属性,不适合遍历数组。for...of:遍历可迭代对象(iterable)的值(value),底层走Symbol.iterator;最适合遍历数组、字符串、Map/Set、NodeList等。- 遍历普通对象(plain object)时:优先
for (const [k, v] of Object.entries(obj));或for...in+Object.hasOwn(obj, k)过滤自有属性。
心智模型:for...in 看“属性枚举”,for...of 看“迭代器协议”
for...in 遍历的到底是什么
for...in 遍历的是:对象上(以及原型链上)可枚举(enumerable)的字符串键。
const obj = Object.create({ inherited: 1 });
obj.own = 2;
for (const k in obj) {
console.log(k);
}
// 可能输出:own inherited(包含原型链上的 enumerable 属性)
如果你只想要“自有属性”,面试里一定要补这一句:
for (const k in obj) {
if (!Object.hasOwn(obj, k)) continue; // 或 obj.hasOwnProperty(k)
// k 一定是自有属性名
}
for...of 遍历的到底是什么
for...of 遍历的是:实现了 iterable 的对象的“迭代结果”,也就是调用 obj[Symbol.iterator]() 返回 iterator,不断 next() 取 { value, done }。
const arr = [10, 20];
for (const v of arr) {
console.log(v); // 10 20
}
普通对象默认不是 iterable,所以不能直接 for...of obj:
const obj = { a: 1 };
for (const x of obj) {
// TypeError: obj is not iterable
}
要遍历对象键值对,推荐:
const obj = { a: 1, b: 2 };
for (const [k, v] of Object.entries(obj)) {
console.log(k, v);
}
典型差异点(高频追问)
1) 遍历数组:为什么不推荐 for...in
for...in 对数组来说会遍历“属性名”,不只包含索引,还可能包含你挂上去的自定义属性,甚至原型链上的 enumerable 属性。
const a = [10, 20];
a.foo = 1;
for (const k in a) console.log(k); // "0" "1" "foo"(顺序也不该依赖)
for (const v of a) console.log(v); // 10 20(只走迭代值)
结论(面试口径):
- 数组遍历:优先
for...of/ 传统for/forEach(不需要 break)等。 for...in更像是“对象属性枚举”,不是“数组元素遍历”。
2) 稀疏数组(holes):for...of 会走,for...in 会跳过
const a = new Array(3); // [ <3 empty items> ]
for (const v of a) console.log(v); // undefined undefined undefined
for (const k in a) console.log(k); // 什么都不输出
面试要点:
for...of是按迭代器按索引推进,取值时“拿不到就是undefined”。for...in枚举的是实际存在的可枚举属性键;洞位没有对应属性键,所以会跳过。
3) Map/Set:for...of 更自然
const m = new Map([
["a", 1],
["b", 2],
]);
for (const [k, v] of m) {
console.log(k, v);
}
const s = new Set([1, 2, 3]);
for (const v of s) console.log(v);
4) 需要 index:for...of 用 entries()
const arr = ["x", "y"];
for (const [i, v] of arr.entries()) {
console.log(i, v); // 0 "x" / 1 "y"
}
典型题 & 标准答法
Q1:遍历对象用哪个更好?
推荐答法:
- 只遍历自有 key/value:
Object.keys/values/entries+for...of。 - 需要包含原型链属性(少见):才考虑
for...in,并明确这是“枚举属性”的语义。
Q2:for...of 还能遍历什么?能不能遍历异步数据?
要点:
- 只要实现了
Symbol.iterator就能for...of(数组、字符串、Map/Set、生成器、DOM 集合等)。 - 异步可迭代用
for await...of(Symbol.asyncIterator),常见于流式数据/异步生成器。
易错点/坑
- 把
for...in当“数组遍历”:容易踩到自定义属性、原型链属性、洞位等问题。 for...in没有自有属性过滤:不加Object.hasOwn可能枚举到继承属性。for...of不能直接遍历普通对象:要用Object.entries或自己实现迭代器。
速记要点(可背诵)
for...in:枚举 key(可枚举属性名,含原型链);对象场景多,数组慎用。for...of:迭代 value(走Symbol.iterator);数组/字符串/Map/Set首选。