【js篇】如何使用 for...of 遍历对象:前端开发中的高级技巧

104 阅读3分钟

在 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]][];
}