JavaScript 中的 for...in
和 for...of
,名字像,功能却天差地别。
混用?代码能跑但极易出 bug。
想记住?咬牙背十次不如一次理解 + 巧记 + 联想。
本篇一文到底,帮你搞懂它们的区别、用途、坑点和记忆技巧。
📦 一张总表,快速建立概念框架
项目 | for...in |
for...of |
---|---|---|
遍历的目标 | 对象、数组(但不推荐) | 可迭代对象(数组、字符串、Set、Map 等) |
遍历的内容 | 键名(属性名) | 值(元素内容) |
本质机制 | 枚举对象的"可枚举属性" | 调用 **[Symbol.iterator]()** 拿值 |
是否包含原型链? | ✅ 会遍历原型链 | ❌ 不会 |
是否能保证顺序? | ❌ 不保证 | ✅ 保证迭代顺序 |
适合对象遍历? | ✅ 是 | ❌ 否(对象不是可迭代的) |
适合数组遍历? | ❌ 慎用,key 是字符串索引 | ✅ 推荐,直接遍历值 |
常见误用风险 | 多遍历出 prototype 的属性 | 无法遍历对象,会直接抛错 |
📌 二、字面理解 ≠ 真正语义:哪里容易误导?
许多人初学时会以为:
- "
in
是在容器里面遍历,应该遍历的是值" - "
of
是属于什么东西,应该遍历的是键名"
👉 其实 完全相反!
巧计: 看字母最后一位!
语句 | 最后一个字母 | 联想 |
---|---|---|
for...in |
n |
n = name = 键名 |
for...of |
f |
f = from = 值是从里面来的 |
🔑 所以------
for...**in**
遍历的是:key(名字)for...**of**
遍历的是:value(值)
🎯 三、实战演练对比:代码最能说明问题
✅ 正确使用 for...in
遍历对象属性:
javascript
const obj = { name: '小吴', age: 28 };
for (const key in obj) {
console.log(`${key}: ${obj[key]}`);
}
// 输出: name: 小吴,age: 28
🚨 不推荐使用 for...in
遍历数组:
ini
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
遍历数组:
arduino
const arr = ['🍎', '🍌', '🍉'];
for (const fruit of arr) {
console.log(fruit); // 直接输出值:🍎 🍌 🍉
}
✅ for...of
可遍历字符串、Set、Map:
javascript
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
本质是这样实现的:
vbnet
const keys = [];
for (let key in obj) {
keys.push(key); // 只抓 key,不抓 value
}
- 只遍历可枚举属性
- 包括继承来的原型链上的属性
for...of
背后的核心是迭代器:
ini
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...in |
for...of |
---|---|---|
遍历对象 | ✅ 适用 | ❌ 不适用 |
遍历数组 | ❌ 慎用 | ✅ 推荐 |
遍历值 | ❌ 键名而非值 | ✅ 真正的元素值 |
原型链属性 | ✅ 会遍历 | ❌ 不会 |
是否迭代器 | ❌ 否 | ✅ 是 |
🎁 八、小结金句(写进脑海)
rust
for in 查的是"键名"(name);
for of 取的是"值"(from容器中);
in 针对对象属性;
of 针对迭代结构;
🧪 配套练习题:从易到难,一网打尽
✅ 基础题 1:这段代码输出什么?
ini
const arr = ['a', 'b', 'c'];
for (let key in arr) {
console.log(key);
}
🧠 你的答案?
✅ 解析:
for...in
遍历的是"键名"- 输出的是:
'0' '1' '2'
(注意是字符串)
✅ 基础题 2:这段代码输出什么?
ini
const arr = ['a', 'b', 'c'];
for (let val of arr) {
console.log(val);
}
✅ 输出: a b c
因为 for...of
直接取值。
⚠️ 中级题 3:输出什么?
ini
const arr = ['x'];
arr.prop = 'extra';
for (let k in arr) console.log('in:', k);
for (let v of arr) console.log('of:', v);
✅ 输出:
makefile
in: 0
in: prop
of: x
🎯 考点解析:
for...in
遍历可枚举属性,包括prop
(添加的自定义属性)for...of
只遍历值(只看[0] = 'x'
)
⚠️ 进阶题 4:报错 or 正常?
ini
const obj = { a: 1, b: 2 };
for (let val of obj) {
console.log(val);
}
❌ 会报错!
🧠 因为:对象不是可迭代的(没有 [Symbol.iterator]
)
✅ 正确方式:
javascript
for (let [k, v] of Object.entries(obj)) {
console.log(k, v);
}
💡 面试题 5:以下输出结果是?
ini
Object.prototype.extra = '原型';
const obj = { name: '小吴', age: 18 };
for (let key in obj) {
console.log(key);
}
✅ 输出:
name
age
extra
🎯 原因:
for...in
会遍历原型链上的可枚举属性- 这就是它不适合在数组中使用的原因之一
🧨 面试陷阱题 6:
ini
const arr = [1, 2, 3];
arr.custom = 'x';
for (let i of arr) console.log(i);
✅ 输出:
1
2
3
🧠 因为 for...of
只管数组本体值,不理 custom 属性。
🎯 面试高频场景 + 回答示例
❓ 问题一:
"请你说一下
for...in
和for...of
的本质区别和使用场景。"
🧠 答题模板(可套用):
markdown
- `for...in` 用于遍历对象的可枚举属性(包括原型链)
- 返回的是 key(属性名)
- 不推荐用于数组,因为顺序不保证,且会遍历原型
- `for...of` 用于遍历可迭代对象(数组、字符串、Map、Set)
- 返回的是 value(元素值)
- 本质依赖 `[Symbol.iterator]` 协议
- 推荐用于数组与类数组结构的遍历
❓ 问题二:
"如果要遍历对象的值,你会用哪个?"
✅ 标准回答:
css
对象不是可迭代的,不能直接用 for...of。
可以先用 Object.entries(obj) 拿到 [key, value] 数组,再用 for...of。
javascript
for (let [k, v] of Object.entries(obj)) {
console.log(k, v);
}