在 JavaScript 中,for...in 和 for...of 是两种常用于遍历的语法,但它们在用途、机制和适用场景上有显著差异。以下从核心区别到实践场景的详细解析:
🔍 一、核心区别
| 特性 | for...in | for...of |
|---|---|---|
| 遍历目标 | 对象的可枚举属性(包括原型链) | 可迭代对象的值(Array, Map, Set等) |
| 输出内容 | 属性名(key/index) | 属性值(value) |
| 适用数据类型 | 普通对象、数组(不推荐) | 数组、字符串、Map、Set 等可迭代对象 |
| 原型链属性 | 默认遍历,需用 hasOwnProperty 过滤 | 不遍历原型链属性 |
| Symbol 属性 | 无法遍历 Symbol 键 | 可遍历 Symbol 键(若对象实现了迭代器) |
示例对比:
const arr = [3, 5, 7]; arr.foo = "自定义属性";
// for...in:输出索引及自定义属性
for (let key in arr) { console.log(key); } // "0", "1", "2", "foo"
// for...of:仅输出值
for (let value of arr) { console.log(value); } // 3, 5, 7
⚙️ 二、工作机制详解
-
for...in的遍历机制:- 遍历对象自身及原型链上所有可枚举属性(非 Symbol 类型)。
- 顺序不固定:数字键优先升序,其他键按添加顺序,但不同引擎可能不一致 [[2][38]]。
- 需用
obj.hasOwnProperty(key)过滤原型链属性:
for (let key in obj) {
if (obj.hasOwnProperty(key)) { console.log(key, obj[key]); // 仅输出自身属性
}
}
for...of 的依赖条件:
- 要求对象实现
Symbol.iterator接口(数组、Map等内置支持)。 - 普通对象不可直接使用,需通过以下方式转换
// 方法1:搭配 Object.keys()
for (let key of Object.keys(obj)) {
console.log(obj[key]);
}
// 方法2:自定义迭代器
obj[Symbol.iterator] = function* () {
for (let key in this) { yield this[key]; }
};
⚠️ 三、关键注意事项
-
遍历顺序问题:
for...in对数组遍历时,顺序可能因引擎差异或数字键排序而混乱,不适合需要顺序的场景 [[1][38]]。for...of严格按可迭代对象的内部顺序输出(如数组按索引顺序)。
-
性能差异:
// 性能测试结果(大数据量下):
for: 21ms // 传统 for 循环最快
forEach: 319ms // 数组方法
for...of: 463ms // 迭代器机制
for...in: 18142ms // 原型链检查导致极慢
-
建议:遍历大数组时优先使用
for或for...of。 -
中断控制:
for...in和for...of均支持break、continue控制流程。forEach等数组方法无法中断
🎯 四、适用场景总结
| 场景 | 推荐语法 | 原因 |
|---|---|---|
| 遍历对象自身属性 | for...in + hasOwnProperty | 精准控制属性来源,避免原型链污染 |
| 遍历数组值 | for...of | 简洁安全,不涉及非元素属性(如自定义属性) |
| 需中断循环的操作 | for...of 或 for | 支持 break/continue,而 forEach 不支持 38 |
| 类数组结构(NodeList等) | for...of | 直接遍历值,无需下标转换 |
| 遍历 Map/Set | for...of | 直接获取键值对(如 [key, value]) |
💎 五、总结建议
-
for...in适用: 对象属性遍历(需过滤原型链),避免用于数组。 -
for...of适用: 数组、字符串、Map/Set 等可迭代对象的值遍历,不支持普通对象(需转换)。 -
性能敏感场景: 大数据量优先选传统
for循环,其次是for...of;禁用for...in遍历数组 。 -
扩展参考: 遍历对象属性可用
Object.keys()+for...of组合,兼顾安全性与现代语法 。
通过理解上述机制与场景差异,可避免常见陷阱(如原型链污染、顺序混乱),写出高效可靠的遍历逻辑。