详细介绍在JavaScript中遍历对象时如何正确获取Symbol类型的属性名,深入解析for...in与Object.keys无法获取Symbol的机制,对比Object.getOwnPropertySymbols与Reflect.ownKeys的使用场景,并提供可枚举过滤与原型链回溯的完整解决方案。
JS遍历对象时怎么获取Symbol类型的属性名?
时间:2026-11-06
浏览:582
文章目录
for...in、Object.keys() 以及 Object.getOwnPropertyNames() 都会默认跳过 Symbol 类型的属性名。获取并遍历 Symbol 属性的核心解法只有两个原生 API:如果你只需要对象的 Symbol 属性,使用 Object.getOwnPropertySymbols(obj);如果你需要同时获取包括 Symbol 在内的对象所有属性,使用 Reflect.ownKeys(obj)。
一、为什么常规遍历方法获取不到 Symbol 属性?
很多开发者在初次使用 Symbol 作为对象键名时,会下意识地使用 for...in 或 Object.keys() 检查对象,结果却发现打印出的键列表里完全没有 Symbol 的踪影。这并不是运行报错,而是 ECMAScript 规范层面的刻意设计。
在 ES6 引入 Symbol 原始数据类型之前,JavaScript 社区积累了大量基于普通字符串键遍历的既有库与业务代码。如果让传统的遍历方法直接输出 Symbol,旧代码在处理属性名(例如拼接字符串或调用字符串原生方法)时就可能发生非预期的类型错误。因此,规范定义:所有常规属性遍历算子对 Symbol 保持隐形。
这种设计使得 Symbol 成为一种非常天然的“轻度私有/元数据”载体,但也要求我们在需要明确遍历它们时,必须调用专有接口。
二、方法一:仅提取对象的 Symbol 属性名
当你的业务意图非常明确——只关心挂载在当前对象上的 Symbol 特殊标识、钩子或元数据时,Object.getOwnPropertySymbols(obj) 是最直接高效的工具。
1. 基本语法与返回值
Object.getOwnPropertySymbols(obj) 接收一个目标对象作为参数,返回一个包含该对象自身所有 Symbol 属性键的数组。不论该 Symbol 属性是否声明为可枚举,它都会被完整收集,而对象的常规字符串属性则会被严格过滤在外。
2. 完整代码示例
// 运行环境:Node.js 12+ 或现代主流浏览器 控制台
const idKey = Symbol('userId');
const roleKey = Symbol('userRole');
const user = {
name: 'Alex',
age: 28,
[idKey]: 10001,
[roleKey]: 'ADMIN'
};
// 1. 常规遍历:无法获得任何 Symbol 属性
console.log('Object.keys 结果:', Object.keys(user));
// 2. 专用 API:获取全部 Symbol 属性名
const symbolKeys = Object.getOwnPropertySymbols(user);
console.log('Symbol 属性名数组:', symbolKeys);
// 3. 循环遍历并读取对应属性值
for (const sym of symbolKeys) {
console.log(`键描述: ${sym.description}, 键名: ${String(sym)}, 值: ${user[sym]}`);
}
Object.keys 结果: [ 'name', 'age' ]
Symbol 属性名数组: [ Symbol(userId), Symbol(userRole) ]
键描述: userId, 键名: Symbol(userId), 值: 10001
键描述: userRole, 键名: Symbol(userRole), 值: ADMIN
如示例所示,通过 Object.getOwnPropertySymbols(user) 拿到数组后,可以自由搭配 for...of、forEach 或 map 完成属性遍历,并通过标准括号语法 user[sym] 顺畅访问对应的属性值。
三、方法二:混合获取对象的全部属性(常规键 + Symbol 键)
在编写通用的深拷贝函数、对象序列化器、属性映射器或 Proxy 代理陷阱(Traps)时,我们通常不希望把属性割裂为“先查普通键、再查 Symbol 键”,而是希望一次性提取对象的全部自身键。此时,Reflect.ownKeys(obj) 是唯一的标准方案。
1. Reflect.ownKeys 的运作机制
Reflect.ownKeys(obj) 在底层等价于 Object.getOwnPropertyNames(target).concat(Object.getOwnPropertySymbols(target))。它以规范定义的严格顺序返回数组:
- 非负整数索引键:按数值升序排列;
- 普通字符串键:按被创建和插入对象的先后顺序排列;
- Symbol 键:按被定义到对象的先后顺序依次附在最后。
2. 完整代码示例
// 运行环境:Node.js 12+ 或现代浏览器环境
const internalKey = Symbol('internal_state');
const config = {
2: 'index_two',
0: 'index_zero',
title: 'ServiceConfig',
[internalKey]: 'ready',
active: true
};
// 获取对象的所有自身键(含 Symbol)
const allKeys = Reflect.ownKeys(config);
console.log('Reflect.ownKeys 返回列表:', allKeys);
// 完整遍历,根据键类型分别处理
allKeys.forEach(key => {
const isSymbol = typeof key === 'symbol';
const label = isSymbol ? key.toString() : key;
console.log(`[类型: ${isSymbol ? 'Symbol' : 'String'}] 键: ${label} => 值: ${config[key]}`);
});
Reflect.ownKeys 返回列表: [ '0', '2', 'title', 'active', Symbol(internal_state) ]
[类型: String] 键: 0 => 值: index_zero
[类型: String] 键: 2 => 值: index_two
[类型: String] 键: title => 值: ServiceConfig
[类型: String] 键: active => 值: true
[类型: Symbol] 键: Symbol(internal_state) => 值: ready
四、进阶场景:枚举性过滤与原型链回溯
无论 Object.getOwnPropertySymbols 还是 Reflect.ownKeys,都有两个共同的关键特征:第一,它们返回自身属性(own properties),不走原型链;第二,它们默认包含不可枚举(non-enumerable)属性。针对实际开发中的特殊需求,需要补充以下两种处理步骤:
1. 只遍历“可枚举”的 Symbol 属性
若希望像 Object.keys 对待普通属性那样,严格仅遍历可枚举的 Symbol 属性,可以配合 Object.prototype.propertyIsEnumerable() 进行过滤:
const publicSym = Symbol('public');
const privateSym = Symbol('private');
const store = {};
// 定义一个可枚举 Symbol 属性
Object.defineProperty(store, publicSym, {
value: '可公开配置',
enumerable: true
});
// 定义一个不可枚举 Symbol 属性
Object.defineProperty(store, privateSym, {
value: '内部保护状态',
enumerable: false
});
// 过滤出仅可枚举的 Symbol
const enumerableSymbols = Object.getOwnPropertySymbols(store).filter(sym =>
Object.prototype.propertyIsEnumerable.call(store, sym)
);
console.log('仅可枚举 Symbol 键:', enumerableSymbols);
仅可枚举 Symbol 键: [ Symbol(public) ]
2. 回溯原型链上的 Symbol 属性
如果目标对象的 Symbol 属性定义在其原型对象(prototype)上,单次调用自身的 API 无法触达。此时应编写一个简单的循环,借助 Object.getPrototypeOf 沿原型链层层回溯:
// 沿原型链递归提取所有 Symbol 属性名
function getAllSymbolKeys(obj) {
const symbols = [];
let current = obj;
while (current && current !== Object.prototype) {
symbols.push(...Object.getOwnPropertySymbols(current));
current = Object.getPrototypeOf(current);
}
return symbols;
}
const protoSym = Symbol('fromProto');
const proto = { [protoSym]: '原型链上的Symbol值' };
const instance = Object.create(proto);
instance[Symbol('fromOwn')] = '自身实例上的Symbol值';
console.log('沿原型链追溯得到的所有 Symbol:', getAllSymbolKeys(instance));
沿原型链追溯得到的所有 Symbol: [ Symbol(fromOwn), Symbol(fromProto) ]
五、避坑提醒与常见注意点
使用
JSON.stringify(obj) 序列化对象时,所有的 Symbol 键以及以 Symbol 作为键对应的值都会被直接忽略丢弃。如果需要序列化带 Symbol 的对象,必须自行通过 Reflect.ownKeys 结合 Replacer 函数转换成普通键值对象。
现代 JavaScript 对象解构中的剩余属性操作符
const { a, ...rest } = obj 会复制对象自身的可枚举 Symbol 属性到新对象中。虽然常规循环忽略它,但浅拷贝展开操作(如 Object.assign 和 { ...obj })会保留可枚举 Symbol。
若通过
Symbol.for('key') 声明 Symbol,遍历获取到的仍是一个普通的 Symbol 实例。若希望获取其在全局注册表中的字符串标识,可调用 Symbol.keyFor(sym) 反查注册名。
实用工具推荐
绿化、压缩、噪点和虚拟水印,自由调节做旧程度。
选择画质和输出格式,预览压缩前后的大小。