Reflect.ownKeys 默认包含对象自有Symbol键,无论是否可枚举;深拷贝时需用Reflect.ownKeys遍历并用方括号赋值,避免for...in或Object.keys遗漏。

Symbol 作为对象键名时,Reflect.ownKeys 默认就能正确返回它——不需要额外处理,但必须理解它的行为边界。
Symbol 键天然被 Reflect.ownKeys 包含
Reflect.ownKeys(obj) 返回对象所有**自有(own)属性键名**的数组,包括:
✅ 字符串键(含不可枚举的)
✅ Symbol 键(含不可枚举的)
❌ 继承属性(无论字符串还是 Symbol)
❌ 非自有、非可枚举的内部属性(如 __proto__)
所以只要 Symbol 是对象自身的属性(即通过 obj[sym] = val 或字面量定义),它就一定会出现在 Reflect.ownKeys 结果中。
例如:
const sym1 = Symbol('a');
const sym2 = Symbol.for('b');
const obj = {
foo: 1,
[sym1]: 'symbol value',
get bar() { return 2; }
};
Object.defineProperty(obj, sym2, { value: 'shared', enumerable: false });
console.log(Reflect.ownKeys(obj));
// ['foo', 'bar', Symbol(a), Symbol(b)] ← 全部自有键,含不可枚举 Symbol
深拷贝中确保 Symbol 键不丢失的关键点
真正容易遗漏 Symbol 键的环节,往往不是 Reflect.ownKeys,而是后续的遍历与赋值逻辑。需注意:
-
不要只用
for...in或Object.keys():它们完全忽略 Symbol 键和不可枚举字符串键 -
遍历
Reflect.ownKeys结果时,用方括号取值:obj[key]对 Symbol 键完全有效;避免用点语法(obj.key不支持 Symbol) -
设置新对象属性时,保持键类型一致:若
key是 Symbol,必须用newObj[key] = ...,不能转成字符串 -
注意 Symbol 的唯一性:用
Symbol.for()创建的全局 Symbol 可跨模块复用;普通Symbol()每次新建都不同,深拷贝时通常只需复制值,无需“还原”原 Symbol 实例(除非有特殊元编程需求)
一个安全的 Symbol-aware 深拷贝片段
以下是最小可行逻辑,聚焦 Symbol 键处理:
function deepClone(obj) {
if (obj === null || typeof obj !== 'object') return obj;
const cloned = Array.isArray(obj) ? [] : {};
const keys = Reflect.ownKeys(obj); // ✅ 包含所有自有键,含 Symbol
for (const key of keys) {
const value = obj[key];
// 递归拷贝值,保持 Symbol 键原样传递
cloned[key] = deepClone(value);
}
return cloned;
}
// 测试
const s = Symbol('id');
const source = { [s]: { nested: true }, name: 'test' };
const copy = deepClone(source);
console.log(copy[s].nested); // true → Symbol 键及值均保留
需要警惕的例外情况
某些场景下 Symbol 键仍可能“消失”,但这和 Reflect.ownKeys 无关,而是设计或使用问题:
-
Proxy 拦截了
ownKeystrap:自定义 handler 若未正确返回 Symbol 键,会导致遗漏 —— 此时需检查 trap 实现 -
冻结/密封对象 + 新增 Symbol 属性:
Object.freeze()后无法新增属性,但已有 Symbol 键不受影响 - JSON 序列化/反序列化:JSON 标准不支持 Symbol,此过程必然丢失 —— 这属于格式限制,不是深拷贝逻辑问题

















