在 JavaScript 中,for...of 循环被设计用于遍历实现了 Symbol.iterator 接口的可迭代对象,如数组、字符串、Map、Set 等。然而,普通对象(Plain Object)默认并不支持 for...of,因为它们没有实现 Symbol.iterator 方法。
但通过一些技巧,我们仍然可以让对象支持 for...of 遍历,或间接实现类似效果。本文将系统介绍多种解决方案。
✅ 一句话总结
普通对象不能直接使用
for...of遍历,但可通过Object.keys/values/entries转为数组,或手动实现Symbol.iterator接口来实现。
✅ 一、为什么普通对象不能直接用 for...of?
const obj = { a: 1, b: 2, c: 3 };
// ❌ 报错:obj is not iterable
for (let item of obj) {
console.log(item);
}
原因:for...of 要求对象必须实现 Symbol.iterator 方法,而普通对象没有这个方法。
console.log(typeof obj[Symbol.iterator]); // undefined
✅ 二、方法一:使用 Object.entries()(推荐 ✅)
将对象的键值对转为数组,再使用 for...of 遍历。
🔹 遍历键值对
const obj = { name: 'Alice', age: 25, city: 'Beijing' };
for (let [key, value] of Object.entries(obj)) {
console.log(`${key}: ${value}`);
}
// 输出:
// name: Alice
// age: 25
// city: Beijing
🔹 仅遍历键或值
// 遍历键
for (let key of Object.keys(obj)) {
console.log(key);
}
// 遍历值
for (let value of Object.values(obj)) {
console.log(value);
}
✅ 优点:简洁、安全、无需修改对象原型。
✅ 三、方法二:为对象手动添加 Symbol.iterator(高级用法)
通过定义 Symbol.iterator 方法,使对象变为可迭代对象。
🔹 示例:遍历对象的值
const obj = { a: 1, b: 2, c: 3 };
// 添加迭代器
obj[Symbol.iterator] = function* () {
for (let key of Object.keys(this)) {
yield this[key];
}
};
// 现在可以使用 for...of
for (let value of obj) {
console.log(value); // 1, 2, 3
}
🔹 示例:遍历键值对
obj[Symbol.iterator] = function* () {
for (let key of Object.keys(this)) {
yield [key, this[key]];
}
};
for (let [k, v] of obj) {
console.log(k, v);
}
⚠️ 注意:
- 修改了对象本身,可能影响其他代码;
- 如果对象是
const声明,属性仍可修改(引用不变); - 不推荐在生产环境随意修改内置或第三方对象原型;
✅ 四、方法三:使用 Map 替代 Object(设计层面优化)
如果需要频繁迭代,建议使用 Map,它天生支持 for...of。
const map = new Map([
['name', 'Alice'],
['age', 25],
['city', 'Beijing']
]);
// 直接支持 for...of
for (let [key, value] of map) {
console.log(key, value);
}
// 也支持 .keys(), .values(), .entries()
for (let key of map.keys()) { /* ... */ }
✅ 优势:
- 插入顺序有序;
- 性能更好(尤其大量数据);
- 天然支持迭代;
✅ 五、方法四:封装可复用的迭代器函数
创建一个通用函数,返回对象的可迭代版本。
function* objectEntries(obj) {
for (let key of Object.keys(obj)) {
yield [key, obj[key]];
}
}
const obj = { x: 10, y: 20 };
for (let [k, v] of objectEntries(obj)) {
console.log(k, v);
}
或返回一个可迭代对象:
function iterableObject(obj) {
return {
*[Symbol.iterator]() {
for (let key of Object.keys(obj)) {
yield [key, obj[key]];
}
}
};
}
for (let [k, v] of iterableObject(obj)) {
console.log(k, v);
}
✅ 六、对比与选择建议
| 方法 | 适用场景 | 优点 | 缺点 |
|---|---|---|---|
Object.entries() | 临时遍历 | 简单、安全、标准 | 每次生成新数组 |
Symbol.iterator | 频繁迭代同一对象 | 一次定义,多次使用 | 修改对象结构 |
Map | 高频读写/迭代 | 高性能、有序、原生支持 | 语法略有不同 |
| 封装函数 | 工具库或通用逻辑 | 可复用、解耦 | 需额外封装 |
✅ 七、一句话总结
虽然普通对象不能直接用
for...of,但通过Object.entries()可轻松实现键值对遍历;若需更高性能或频繁迭代,推荐使用Map或手动实现Symbol.iterator。
💡 最佳实践
- 优先使用
Object.entries(obj):最安全、最通用; - 考虑使用
Map:当对象主要用于存储键值对且需要迭代时; - 避免污染原型:不要随意给
Object.prototype添加Symbol.iterator; - TypeScript 用户:可结合类型守卫确保对象结构;
function safeObjectEntries<T extends Record<string, any>>(obj: T) {
return Object.entries(obj) as [keyof T, T[keyof T]][];
}