跳到主要内容

谈谈你对 for...in / for...of 的理解?分别适用于什么场景?

面试速答(30 秒版 TL;DR)

  • for...in:遍历对象的可枚举属性名(key),key 是字符串;会包含原型链上可枚举属性,不适合遍历数组。
  • for...of:遍历可迭代对象(iterable)的值(value),底层走 Symbol.iterator;最适合遍历数组、字符串、Map/SetNodeList 等。
  • 遍历普通对象(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/Setfor...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...ofentries()

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...ofSymbol.asyncIterator),常见于流式数据/异步生成器。

易错点/坑

  • for...in 当“数组遍历”:容易踩到自定义属性、原型链属性、洞位等问题。
  • for...in 没有自有属性过滤:不加 Object.hasOwn 可能枚举到继承属性。
  • for...of 不能直接遍历普通对象:要用 Object.entries 或自己实现迭代器。

速记要点(可背诵)

  • for...in:枚举 key(可枚举属性名,含原型链);对象场景多,数组慎用。
  • for...of:迭代 value(走 Symbol.iterator);数组/字符串/Map/Set 首选。