标准答案

  1. for...in:遍历对象自身和原型链上的可枚举字符串属性。遍历普通对象时通常要配合 Object.hasOwn 过滤继承属性,不推荐用它遍历数组。
  2. Object.keys、getOwnPropertyNames、Reflect.ownKeys:Object.keys 只返回自身可枚举字符串属性;Object.getOwnPropertyNames 返回自身字符串属性,包括不可枚举属性;Reflect.ownKeys 返回自身所有属性键,包括字符串键和 Symbol 键。
  3. for...of:遍历可迭代对象的迭代值,例如数组、字符串、Map、Set、NodeList。普通对象默认没有迭代器,不能直接 for...of。
  4. 选择方式:遍历普通对象自身业务字段,用 Object.keys 或 Object.entries;需要包含 Symbol 或不可枚举键,用 Reflect.ownKeys;遍历数组、Map、Set 的值,用 for...of。

题目解析

这里的核心是区分两条线:一条是“属性枚举”,一条是“迭代协议”。for...in、Object.keys、Reflect.ownKeys 关注对象属性;for...of 关注对象是否实现了可迭代协议。

for...in 会遍历原型链上的可枚举字符串属性,这是它最容易出错的地方。真实业务里遍历普通对象更常用 Object.keys/Object.entries,因为它们只看自身可枚举属性,结果更可控。

Reflect.ownKeys 的覆盖面最完整,会包含自身字符串键和 Symbol 键,也包含不可枚举属性。它适合元编程、工具库、复制对象属性等场景,但普通业务列表渲染一般不需要这么宽的范围。

对象属性顺序规则可以作为补充,但不要把顺序规则当核心。真正要先判断的是“要遍历什么”:自有可枚举属性、所有自有键,还是可迭代值。

代码示例

下面例子能说明 for...in 会看到继承的可枚举属性,而 Object.keys 只看自身可枚举属性。

JavaScript
const proto = { inherited: true }
const obj = Object.create(proto)
obj.name = 'Alice'
Object.defineProperty(obj, 'hidden', {
  value: 1,
  enumerable: false
})

for (const key in obj) {
  console.log(key) // name, inherited
}

Object.keys(obj) // ['name']
Reflect.ownKeys(obj) // ['name', 'hidden']

常见误区

  • 用 for...in 遍历数组,可能遍历到自定义属性或原型属性。
  • 误以为普通对象默认可以 for...of。
  • 忘记 Symbol 键不会出现在 Object.keys 里。

作者信息