for...in 和 for...of 的终极区别:一文彻底解决你的困惑

524 阅读5分钟

JavaScript 中的 for...infor...of,名字像,功能却天差地别。
混用?代码能跑但极易出 bug。
想记住?咬牙背十次不如一次理解 + 巧记 + 联想。

本篇一文到底,帮你搞懂它们的区别、用途、坑点和记忆技巧。


📦 一张总表,快速建立概念框架

项目for...infor...of
遍历的目标对象、数组(但不推荐)可迭代对象(数组、字符串、Set、Map 等)
遍历的内容键名(属性名)值(元素内容)
本质机制枚举对象的“可枚举属性调用 **[Symbol.iterator]()** 拿值
是否包含原型链?✅ 会遍历原型链❌ 不会
是否能保证顺序?❌ 不保证✅ 保证迭代顺序
适合对象遍历?✅ 是❌ 否(对象不是可迭代的)
适合数组遍历?❌ 慎用,key 是字符串索引✅ 推荐,直接遍历值
常见误用风险多遍历出 prototype 的属性无法遍历对象,会直接抛错

📌 二、字面理解 ≠ 真正语义:哪里容易误导?

许多人初学时会以为:

  • in 是在容器里面遍历,应该遍历的是
  • of 是属于什么东西,应该遍历的是键名

👉 其实 完全相反

巧计: 看字母最后一位!

语句最后一个字母联想
for...innn = name = 键名
for...offf = from = 值是从里面来的

🔑 所以——

  • for...**in** 遍历的是:key(名字)
  • for...**of** 遍历的是:value(值)

🎯 三、实战演练对比:代码最能说明问题

✅ 正确使用 for...in 遍历对象属性:

const obj = { name: '小吴', age: 28 };
for (const key in obj) {
  console.log(`${key}: ${obj[key]}`);
}
// 输出: name: 小吴,age: 28

🚨 不推荐使用 for...in 遍历数组:

Array.prototype.extra = '扩展';
const arr = ['🍎', '🍌'];

for (const index in arr) {
  console.log(index); // 输出:'0', '1', 'extra' ❌
}

📉 为什么错?因为:

  • for...in 会遍历到原型上的可枚举属性

  • 遍历的是 字符串索引('0', '1') ,不是数字

  • 数组本质上是对象:

    • 数组是一种拥有特殊键(数字索引)和 length 属性的对象。
    • 数组中的索引如 0, 1, 2 都是对象的属性名,实质上是 "0", "1", "2" 这些字符串键。
  • for...in 遍历的是对象的可枚举属性名(字符串),包括数组的索引。


✅ 正确使用 for...of 遍历数组:

const arr = ['🍎', '🍌', '🍉'];

for (const fruit of arr) {
  console.log(fruit); // 直接输出值:🍎 🍌 🍉
}

for...of 可遍历字符串、Set、Map:

for (const ch of 'Hello') console.log(ch); // H e l l o

for (const item of new Set([1, 2, 3])) console.log(item); // 1 2 3

for (const [key, val] of new Map([['a', 1], ['b', 2]]))
  console.log(key, val); // a 1, b 2

🧠 四、记忆技巧:一次记牢

✅ 技巧 1:看字母结尾

  • **in** 结尾是 n → name(键名)
  • **of** 结尾是 f → from(从容器中拿值)

✅ 技巧 2:角色隐喻法(不剧情)

语法角色类比它关注什么?
for...in图书管理员看“书名”(key)
for...of图书借阅员直接“翻书内容”(value)

✅ 技巧 3:出场规则口诀

“in 看名,of 看值;in 查对象,of 点人头。”


🔬 五、底层机制区别

for...in 本质是这样实现的:

const keys = [];
for (let key in obj) {
  keys.push(key); // 只抓 key,不抓 value
}
  • 只遍历可枚举属性
  • 包括继承来的原型链上的属性

for...of 背后的核心是迭代器:

const iterable = ['a', 'b'];
const iterator = iterable[Symbol.iterator]();
console.log(iterator.next()); // { value: 'a', done: false }

所有可迭代对象都必须实现 [Symbol.iterator]() 方法

注意:!!!所以对象默认不是可迭代的

若你想对对象用 for...of,请先转为可迭代结构(如 entries()、自定义 iterator、Map)。


🧪 六、典型错误总结(能避坑)

错误代码错因替代方案
for (x in [1, 2])遍历字符串索引 + 可能包含原型属性改用 for...of
for (x of { a: 1 })对象不是 iterable,会直接报错Object.entries() + for...of
遍历 Map 不解构 for (item of map)item 是 [key, value] 数组使用 [k, v] of map

📌 七、终极总结

特性for...infor...of
遍历对象✅ 适用❌ 不适用
遍历数组❌ 慎用✅ 推荐
遍历值❌ 键名而非值✅ 真正的元素值
原型链属性✅ 会遍历❌ 不会
是否迭代器❌ 否✅ 是

🎁 八、小结金句(写进脑海)

for in 查的是“键名”(name);
for of 取的是“值”(from容器中);
in 针对对象属性;
of 针对迭代结构;

🧪 配套练习题:从易到难,一网打尽


✅ 基础题 1:这段代码输出什么?

const arr = ['a', 'b', 'c'];
for (let key in arr) {
  console.log(key);
}

🧠 你的答案?

解析:

  • for...in 遍历的是“键名”
  • 输出的是:'0' '1' '2'(注意是字符串)

✅ 基础题 2:这段代码输出什么?

const arr = ['a', 'b', 'c'];
for (let val of arr) {
  console.log(val);
}

输出: a b c
因为 for...of 直接取值。


⚠️ 中级题 3:输出什么?

const arr = ['x'];
arr.prop = 'extra';

for (let k in arr) console.log('in:', k);
for (let v of arr) console.log('of:', v);

输出:

in: 0
in: prop
of: x

🎯 考点解析:

  • for...in 遍历可枚举属性,包括 prop(添加的自定义属性)
  • for...of 只遍历值(只看 [0] = 'x'

⚠️ 进阶题 4:报错 or 正常?

const obj = { a: 1, b: 2 };
for (let val of obj) {
  console.log(val);
}

会报错!

🧠 因为:对象不是可迭代的(没有 [Symbol.iterator]

✅ 正确方式:

for (let [k, v] of Object.entries(obj)) {
  console.log(k, v);
}

💡 面试题 5:以下输出结果是?

Object.prototype.extra = '原型';

const obj = { name: '小吴', age: 18 };
for (let key in obj) {
  console.log(key);
}

输出:

name
age
extra

🎯 原因:

  • for...in 会遍历原型链上的可枚举属性
  • 这就是它不适合在数组中使用的原因之一

🧨 面试陷阱题 6:

const arr = [1, 2, 3];
arr.custom = 'x';

for (let i of arr) console.log(i);

✅ 输出:

1
2
3

🧠 因为 for...of 只管数组本体值,不理 custom 属性。


🎯 面试高频场景 + 回答示例


❓ 问题一:

“请你说一下 for...infor...of 的本质区别和使用场景。”

🧠 答题模板(可套用):

- `for...in` 用于遍历对象的可枚举属性(包括原型链)
  - 返回的是 key(属性名)
  - 不推荐用于数组,因为顺序不保证,且会遍历原型
- `for...of` 用于遍历可迭代对象(数组、字符串、Map、Set)
  - 返回的是 value(元素值)
  - 本质依赖 `[Symbol.iterator]` 协议
  - 推荐用于数组与类数组结构的遍历

❓ 问题二:

“如果要遍历对象的值,你会用哪个?”

✅ 标准回答:

对象不是可迭代的,不能直接用 for...of。
可以先用 Object.entries(obj) 拿到 [key, value] 数组,再用 for...of
for (let [k, v] of Object.entries(obj)) {
  console.log(k, v);
}