JS遍历对象时怎么获取Symbol类型的属性名?

作者:异步漫步

时间:2026-11-06

浏览:582

详细介绍在JavaScript中遍历对象时如何正确获取Symbol类型的属性名,深入解析for...in与Object.keys无法获取Symbol的机制,对比Object.getOwnPropertySymbols与Reflect.ownKeys的使用场景,并提供可枚举过滤与原型链回溯的完整解决方案。

文章目录
在 JavaScript 中遍历对象时,常见的 for...in、Object.keys() 以及 Object.getOwnPropertyNames() 都会默认跳过 Symbol 类型的属性名。获取并遍历 Symbol 属性的核心解法只有两个原生 API:如果你只需要对象的 Symbol 属性,使用 Object.getOwnPropertySymbols(obj);如果你需要同时获取包括 Symbol 在内的对象所有属性,使用 Reflect.ownKeys(obj)。
JS遍历对象时怎么获取Symbol类型的属性名?
图 0:JavaScript 普通属性与 Symbol 属性遍历 API 全貌一览

一、为什么常规遍历方法获取不到 Symbol 属性?

很多开发者在初次使用 Symbol 作为对象键名时,会下意识地使用 for...in 或 Object.keys() 检查对象,结果却发现打印出的键列表里完全没有 Symbol 的踪影。这并不是运行报错,而是 ECMAScript 规范层面的刻意设计。

在 ES6 引入 Symbol 原始数据类型之前,JavaScript 社区积累了大量基于普通字符串键遍历的既有库与业务代码。如果让传统的遍历方法直接输出 Symbol,旧代码在处理属性名(例如拼接字符串或调用字符串原生方法)时就可能发生非预期的类型错误。因此,规范定义:所有常规属性遍历算子对 Symbol 保持隐形。

这种设计使得 Symbol 成为一种非常天然的“轻度私有/元数据”载体,但也要求我们在需要明确遍历它们时,必须调用专有接口。

JS遍历对象时怎么获取Symbol类型的属性名?
图 1:JavaScript 对象属性遍历与检索边界对比矩阵

二、方法一:仅提取对象的 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] 顺畅访问对应的属性值。

JS遍历对象时怎么获取Symbol类型的属性名?
图 2:获取与遍历 Symbol 属性的标准操作决策流程

三、方法二:混合获取对象的全部属性(常规键 + Symbol 键)

在编写通用的深拷贝函数、对象序列化器、属性映射器或 Proxy 代理陷阱(Traps)时,我们通常不希望把属性割裂为“先查普通键、再查 Symbol 键”,而是希望一次性提取对象的全部自身键。此时,Reflect.ownKeys(obj) 是唯一的标准方案。

1. Reflect.ownKeys 的运作机制

Reflect.ownKeys(obj) 在底层等价于 Object.getOwnPropertyNames(target).concat(Object.getOwnPropertySymbols(target))。它以规范定义的严格顺序返回数组:

  1. 非负整数索引键:按数值升序排列;
  2. 普通字符串键:按被创建和插入对象的先后顺序排列;
  3. 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
JS遍历对象时怎么获取Symbol类型的属性名?
图 3:JavaScript 引擎对象内部存储结构与属性检索访问机制

四、进阶场景:枚举性过滤与原型链回溯

无论 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) ]
JS遍历对象时怎么获取Symbol类型的属性名?
图 4:JavaScript 对象属性遍历与检索全景总结

五、避坑提醒与常见注意点

注意点 1:JSON.stringify 的完全忽略
使用 JSON.stringify(obj) 序列化对象时,所有的 Symbol 键以及以 Symbol 作为键对应的值都会被直接忽略丢弃。如果需要序列化带 Symbol 的对象,必须自行通过 Reflect.ownKeys 结合 Replacer 函数转换成普通键值对象。
注意点 2:解构赋值中的 Rest 语法行为
现代 JavaScript 对象解构中的剩余属性操作符 const { a, ...rest } = obj 会复制对象自身的可枚举 Symbol 属性到新对象中。虽然常规循环忽略它,但浅拷贝展开操作(如 Object.assign 和 { ...obj })会保留可枚举 Symbol。
注意点 3:全局 Symbol 注册表的读取
若通过 Symbol.for('key') 声明 Symbol,遍历获取到的仍是一个普通的 Symbol 实例。若希望获取其在全局注册表中的字符串标识,可调用 Symbol.keyFor(sym) 反查注册名。

实用工具推荐

电子包浆

绿化、压缩、噪点和虚拟水印,自由调节做旧程度。

使用 ›
图片压缩

选择画质和输出格式,预览压缩前后的大小。

使用 ›
相关文章