Reflect.ownKeys 是完美复制业务对象的关键起点,它能返回字符串键和 Symbol 键等所有自身属性键,再配合 getOwnPropertyDescriptor 和 defineProperty 才可完整保留属性特性与访问器。

要完美复制一个包含所有非标准键名(比如 Symbol 键、不可枚举属性、带空格或特殊字符的字符串键)的业务对象,Reflect.ownKeys 是关键起点——它能返回对象自身所有属性键(包括字符串、Symbol、不可枚举键),但仅靠它还不够,需配合 getOwnPropertyDescriptor 和 defineProperty 才能保留属性特性(如 enumerable、writable、configurable、value 或 get/set)。
为什么 Object.keys / Object.getOwnPropertyNames 不够用?
它们都忽略 Symbol 类型的键:
- Object.keys(obj) 只返回可枚举的字符串键;
- Object.getOwnPropertyNames(obj) 返回所有字符串键(含不可枚举),但仍漏掉 Symbol 键;
- 而 Reflect.ownKeys(obj) 同时返回所有字符串键和 Symbol 键,且顺序与 for...in 一致(先字符串键按创建顺序,再 Symbol 键按插入顺序),是真正“完整”的键列表。
完整复制:保留键类型 + 属性描述符
仅遍历键名并用 obj[key] 取值会丢失:
- 访问器属性(get/set)被降级为普通值;
- enumerable: false、writable: false 等元信息丢失;
- 原始对象的原型链、不可配置性等也不体现。
正确做法是逐个获取并重定义属性描述符:
- 用
Reflect.ownKeys(obj)获取全部键 - 对每个键调用
Object.getOwnPropertyDescriptor(obj, key) - 用
Object.defineProperty(target, key, descriptor)复制到新对象 - 若需继承原型,最后用
Object.setPrototypeOf(newObj, Object.getPrototypeOf(obj))
实用封装函数(支持 Symbol、访问器、不可枚举)
下面是一个轻量、无依赖的深拷贝变体(浅层完整复制):
function fullShallowCopy(obj) {
if (obj === null || typeof obj !== 'object') return obj;
<p>const target = Array.isArray(obj) ? [] : {};
const keys = Reflect.ownKeys(obj);</p><p>for (const key of keys) {
const desc = Object.getOwnPropertyDescriptor(obj, key);
Object.defineProperty(target, key, desc);
}</p><p>Object.setPrototypeOf(target, Object.getPrototypeOf(obj));
return target;
}</p>✅ 支持:
- 字符串键(含空格、中文、emoji)
- Symbol('id') 等 Symbol 键
- get name() { return this._name; } 访问器
- Object.defineProperty(obj, 'hidden', { value: 42, enumerable: false })
❌ 不处理嵌套对象/数组的深层复制(如需深度,请在此基础上递归调用 fullShallowCopy)
注意边界情况
某些场景需额外判断:
-
不可扩展对象(sealed/frozen):复制后默认是可扩展的;若需保持冻结状态,复制完需手动
Object.freeze(target) -
代理对象(Proxy):
Reflect.ownKeys返回的是 proxy 的 trap 结果,不是原始目标的键;此时应操作proxy.target(如果可访问) -
稀疏数组或 length 非常规值:
Array.isArray(obj)分支已处理,但若原数组有不可枚举的数字索引(极少见),也会被正确捕获










