for...of
循环无法输出对象的自定义属性(如 hello
),是由其底层设计原理决定的。
一、核心原因:for...of
仅遍历可迭代对象的值
for...of
是 ES6 引入的语法,专门用于遍历实现了迭代器接口(Symbol.iterator
)的对象(如数组、Map、Set 等)。它的工作逻辑是:
- 调用对象的迭代器方法 :
const iterator = obj[Symbol.iterator]();
- 依次获取值 :
iterator.next().value
- 不涉及对象属性的遍历。
普通对象(如 {}
)默认没有实现迭代器接口 ,因此无法直接被 for...of
遍历。尝试遍历会直接报错:
arduino
const obj = { hello: "world" };
for (const item of obj) {} // TypeError: obj is not iterable
底层原理 :
for...of
依赖对象的@@iterator
方法(通过Symbol.iterator
访问),普通对象原型链上无此方法 。
二、对比 for...in
:为何能输出自定义属性?
for...in
的设计目标是遍历对象的可枚举属性(包括自定义属性):
- 遍历范围:对象自身属性 + 原型链上的可枚举属性。
- 输出键名:循环变量是属性名(字符串类型)。
vbnet
const obj = { hello: "world" };
for (const key in obj) {
console.log(key); // 输出 "hello"
}
关键区别 :
for...in
是属性枚举机制,而for...of
是迭代器协议的值消费机制
三、如何让 for...of
遍历对象属性?
需手动实现迭代器接口,将对象转换为可迭代结构:
javascript
javascript
复制
const obj = {
hello: "world",
[Symbol.iterator]() { // 实现迭代器
const keys = Object.keys(this);
let index = 0;
return {
next: () => ({
value: this[keys[index++]], // 返回属性值
done: index > keys.length
})
};
}
};
for (const value of obj) {
console.log(value); // 输出 "world"
}
此方法将对象的属性值转为可迭代序列 。
四、设计哲学:为何如此区分?
-
语义分离:
for...in
→ 遍历属性名(适用于对象配置、键值对操作)for...of
→ 遍历元素值(适用于数组、集合等数据序列)。
-
避免意外行为:
for...of
不遍历原型链属性,防止污染数据。
-
性能优化:
- 迭代器协议(如数组)比属性枚举更高效。
总结:关键差异速查表
特性 | for...of |
for...in |
---|---|---|
遍历目标 | 可迭代对象的元素值 | 对象的可枚举属性名 |
支持普通对象 | ❌(需手动实现迭代器) | ✅ |
输出内容 | 元素值(如数组项、Map值) | 属性名(字符串) |
遍历原型链属性 | ❌ | ✅(需用 hasOwnProperty 过滤) |
最佳实践:
- 遍历数组/Map/Set → 用
for...of
(直接获取值)- 遍历对象属性 → 用
for...in
(或Object.keys()
+for...of
)。