FumadocsZDecode
JavaScript

对象遍历

JavaScript 提供了多种遍历对象属性的方法,每种方法覆盖的属性范围不同。理解它们的区别,关键在于掌握三类属性的概念。

三类属性

const obj = {
  name: 'Alice',                   // 可枚举字符串 key
  [Symbol('id')]: 'sym123',        // Symbol key
}

Object.defineProperty(obj, 'hidden', {
  value: 'secret',
  enumerable: false,               // 不可枚举字符串 key
})
属性类型说明
可枚举字符串 key普通赋值的属性,参与大多数遍历
不可枚举字符串 key通过 Object.defineProperty 设置 enumerable: false
Symbol key永远不出现在默认遍历中,需专用方法访问

遍历方法总览

方法遍历目标继承属性Symbol不可枚举
for...in字符串 key
Object.keys()字符串 key
Object.values()value 值
Object.entries()[key, value]
Object.getOwnPropertyNames()字符串 key
Object.getOwnPropertySymbols()Symbol key
Reflect.ownKeys()所有 key

标准遍历

这四个方法只遍历自身可枚举的字符串 key(Symbol 和不可枚举属性均不包含)。

for...in

遍历对象自身及原型链上所有可枚举的字符串 key。

const parent = { x: 10 }
const child = Object.create(parent)
child.y = 20
child[Symbol('id')] = 'hidden'

for (const key in child) {
  console.log(key) // 'y', 'x'(包含继承的 x,不含 Symbol)
}

如果只需要自身属性,用 hasOwn 过滤:

for (const key in child) {
  if (Object.hasOwn(child, key)) {
    console.log(key) // 仅 'y'
  }
}

Object.keys()

返回自身可枚举字符串 key 的数组,不含继承属性。

const obj = { name: 'Alice', age: 25, [Symbol('id')]: 'hidden' }

Object.keys(obj) // ['name', 'age']

Object.values()

返回自身可枚举属性的值数组。

Object.values(obj) // ['Alice', 25]

Object.entries()

返回 [key, value] 对的数组,常用于对象与 Map 之间的转换。

Object.entries(obj) // [['name', 'Alice'], ['age', 25]]

// Object 转 Map
const map = new Map(Object.entries(obj))

// Map 转 Object
const restored = Object.fromEntries(map)

完整遍历

当需要访问不可枚举属性或 Symbol key 时,使用以下方法。

Object.getOwnPropertyNames()

返回所有自身字符串 key,包括不可枚举,但不含 Symbol。

const obj = { visible: 'yes' }
Object.defineProperty(obj, 'secret', { value: 'shh', enumerable: false })
obj[Symbol('id')] = 'sym'

Object.getOwnPropertyNames(obj) // ['visible', 'secret']

Object.getOwnPropertySymbols()

返回所有自身 Symbol key,包括不可枚举的 Symbol,不含字符串 key。

const id = Symbol('id')
const tag = Symbol('tag')

const obj = { name: 'Alice', [id]: '123', [tag]: 'vip' }
Object.defineProperty(obj, 'hidden', { value: 'x', enumerable: false })

Object.getOwnPropertySymbols(obj) // [Symbol(id), Symbol(tag)]

Reflect.ownKeys()

返回所有自身 key:字符串(含不可枚举)+ Symbol(含不可枚举)。覆盖范围最完整。

const obj = { name: 'Alice' }
Object.defineProperty(obj, 'secret', { value: 'shh', enumerable: false })
obj[Symbol('id')] = '123'

Reflect.ownKeys(obj) // ['name', 'secret', Symbol(id)]

Reflect.ownKeys 等价于 Object.getOwnPropertyNames + Object.getOwnPropertySymbols 的合并结果。

Symbol 作为 Key

Symbol 被设计为"不可见"的属性键,不参与任何标准遍历,只能通过专用方法访问。

Symbol key 的不可见性

const sym = Symbol('id')
const obj = { name: 'Alice', [sym]: '123' }

// 以下方法都看不到 Symbol key
for (const key in obj) {}           // 'name'
Object.keys(obj)                    // ['name']
Object.getOwnPropertyNames(obj)     // ['name']
JSON.stringify(obj)                 // {"name":"Alice"}
const spread = { ...obj }           // { name: 'Alice' },Symbol 丢失

// 必须用专用方法
Object.getOwnPropertySymbols(obj)   // [Symbol(id)]
Reflect.ownKeys(obj)                // ['name', Symbol(id)]
obj[sym]                            // '123'

{ ...obj } 展开运算符不会复制 Symbol key。需要保留 Symbol 时,用 Object.assign({}, obj) 替代。

const clone = Object.assign({}, obj)
Object.getOwnPropertySymbols(clone) // [Symbol(id)] ✅

Symbol 与 JSON

JSON.stringify 会静默忽略 Symbol key。如果需要序列化 Symbol,需自定义 replacer:

const obj = { name: 'Alice', [Symbol('id')]: '123' }

JSON.stringify(obj)
// '{"name":"Alice"}'   — Symbol 被丢弃

JSON.stringify(obj, (key, value) => {
  if (typeof key === 'symbol') return `[Symbol: ${key.description}]`
  return value
})
// '{"name":"Alice","[Symbol: id]":"123"}'

本地 Symbol vs 全局 Symbol

// 本地 Symbol:每次创建都唯一,无法跨模块共享
const sym1 = Symbol('id')
const sym2 = Symbol('id')
sym1 === sym2 // false

const obj = { [sym1]: 'value' }
obj[sym2] // undefined

// 全局 Symbol:通过 Symbol.for() 注册,相同 key 返回同一实例
const g1 = Symbol.for('app.id')
const g2 = Symbol.for('app.id')
g1 === g2       // true
obj[g2]         // 可正常访问

Symbol.keyFor(g1)             // 'app.id'
Symbol.keyFor(Symbol('local')) // undefined(本地 Symbol 不在全局注册表中)

跨模块或跨 iframe 共享 Symbol 时,使用 Symbol.for();需要模块私有的不可见属性时,使用本地 Symbol()

综合示例

const sym = Symbol('id')
const obj = { name: 'Alice', age: 25, [sym]: '123' }
Object.defineProperty(obj, 'secret', { value: 'hidden', enumerable: false })
Object.setPrototypeOf(obj, { inherited: 'yes' })

Object.keys(obj)                    // ['name', 'age']
Object.getOwnPropertyNames(obj)     // ['name', 'age', 'secret']
Object.getOwnPropertySymbols(obj)   // [Symbol(id)]
Reflect.ownKeys(obj)                // ['name', 'age', 'secret', Symbol(id)]

// for...in 包含继承属性
const keys = []
for (const key in obj) keys.push(key)
keys // ['name', 'age', 'inherited']

On this page