前言
在 JavaScript 中,遍历数据是我们每天都在做的事情。但是,你真的用对了吗?为什么有时候遍历数组会出现奇怪的属性?为什么对象不能用 for...of?本文带你从底层机制区分这两个“双胞胎”。
一、 核心区别速览(一张表看懂)
简单来说:for...in 是为遍历对象属性而构建的,不建议用于数组;for...of 是为遍历迭代器(Iterator)对象而构建的。
| 特性 | for...in | for...of (ES6) | |
|---|---|---|---|
| 遍历目标 | 对象 (Object) | 可迭代对象 (Array, Map, Set, String...) | |
| 获取内容 | 键名 (Key) | 键值 (Value) | |
| 推荐场景 | 调试、查看对象属性 | 遍历数组、类数组、Map/Set |
二、 for...in:对象的“老管家”
for...in 循环主要用于遍历对象的可枚举属性(enumerable properties)。
1. 遍历数组的“坑”
虽然它能遍历数组,但强烈不推荐。
- 索引是字符串:输出的
0,1是字符串类型,不是数字,进行计算容易出错。 - 原型链污染:如果有人在
Array.prototype上加了方法,for...in会把这个方法也遍历出来!
Array.prototype.sayHello = function() {
console.log("Hello");
}
const arr = ['a', 'b'];
for (let key in arr) {
console.log(key);
// 输出: "0", "1", "sayHello"
// (看!多了一个不想要的东西)
}
2. 正确用法:遍历对象
const obj = { name: 'ouyang', age: 12 };
for (let key in obj) {
// 建议加上 hasOwnProperty 过滤原型链属性
if (obj.hasOwnProperty(key)) {
console.log(key, obj[key]); // name ouyang, age 12
}
}
三、 for...of:数组的“新宠儿”
for...of 是 ES6 引入的新语法,它专门用于 Iterator(迭代器) 接口。
1. 强大的兼容性
只要一个对象部署了 [Symbol.iterator] 属性,它就能用 for...of 遍历。这意味着它不仅支持数组,还支持:
- 字符串 (String)
- Map 和 Set
- arguments 对象
- NodeList (DOM 列表)
// 遍历字符串
for (let char of "Hello") {
console.log(char); // "H", "e", "l", "l", "o"
}
2. 为什么遍历对象会报错?
const obj = { name: 'ouyang', age: 12 };
// for (let item of obj) {}
// ❌ Uncaught TypeError: obj is not iterable
原因:普通对象(Object)默认没有部署 Iterator 接口。
解决:结合 Object.keys() 使用。
for (let key of Object.keys(obj)) {
console.log(key + ': ' + obj[key]);
}
四、 面试模拟题
Q1:为什么不建议使用 for...in 遍历数组?
参考回答:
- 原型链干扰:
for...in会遍历数组原型链上所有可枚举的属性,如果原型被扩展(如添加了自定义方法),这些属性也会被遍历出来,导致意外错误。 - 类型问题:它返回的索引是 String 类型(如 "0"),而不是 Number,这在涉及索引计算时容易产生 Bug(例如 "1" + "2" = "12")。
- 语义不符:它的设计初衷是遍历对象的键,而不是数组的项。
Q2:for...of 可以遍历对象吗?如果非要遍历怎么办?
参考回答:
直接使用 for...of 遍历普通对象会报错,因为对象没有 Symbol.iterator 接口。
如果非要用,有两种方法:
- 间接遍历:配合
Object.keys(obj)、Object.values(obj)或Object.entries(obj)生成数组后遍历。 - 自定义迭代器:给对象手动添加
[Symbol.iterator]属性(属于高阶玩法)。
Q3:forEach、for...in、for...of 有什么区别?
参考回答:
forEach:数组的高阶方法,无法使用break或return跳出循环。for...in:遍历键名,会遍历原型链,适合对象。for...of:遍历键值,支持 break,适合数组和所有可迭代对象。