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']