object.defineproperties() 不用于克隆,而是配合 object.getownpropertydescriptors() 和 object.create() 实现“像素级”克隆:先获取自有属性完整描述符,再创建同原型空对象,最后批量还原所有属性特性。

Object.defineProperties() 本身不用于克隆,它只负责批量定义或修改对象的属性特性。所谓“精准像素级”克隆,核心不是靠它完成复制,而是用它**精确还原源对象每个属性的底层行为**——包括可枚举性、可配置性、可写性、getter/setter、甚至不可枚举的内置属性(如 length 在数组上)。真正的克隆动作需配合 Object.getOwnPropertyDescriptors() 和 Object.create() 等组合使用。
先获取完整属性描述符,不止是值
普通 JSON.parse(JSON.stringify()) 或展开运算符会丢失函数、undefined、Symbol 键、getter/setter、不可枚举属性等。Object.getOwnPropertyDescriptors(obj) 能一次性拿到对象**自有属性**的全部描述符(包括 value、writable、enumerable、configurable、get、set),这才是“像素级”的数据基础。
例如:
const src = {a: 1,
get b() { return this.a * 2; },
set c(v) { this._c = v; }
};
Object.defineProperty(src, 'd', { value: 4, enumerable: false });
// getOwnPropertyDescriptors 返回:
{
a: { value: 1, writable: true, enumerable: true, configurable: true },
b: { get: [Getter], set: undefined, enumerable: true, configurable: true },
c: { get: undefined, set: [Setter], enumerable: true, configurable: true },
d: { value: 4, writable: false, enumerable: false, configurable: false }
}
用 Object.create() 创建原型一致的空壳
直接 {} 会丢失原型链。要保持继承关系(比如 Date、Array、自定义类实例),必须用 Object.create(Object.getPrototypeOf(src)) 创建一个原型相同但无自有属性的新对象。这是“像素级”保真度的关键一步。
- 若源对象是普通对象(
Object.prototype为原型),Object.create(null)也可,但会断开原型链,慎用 - 若源对象是
Array实例,Object.getPrototypeOf(src)是Array.prototype,新对象才能拥有push、map等方法
再用 Object.defineProperties() 批量注入描述符
把上一步拿到的描述符对象传给 Object.defineProperties(target, descriptors),就能逐个还原每个属性的全部元信息——包括不可枚举的 d、只读的 length、带 getter 的 b 等。此时目标对象在行为层面与源对象几乎完全一致。
示例整合:
function deepClonePrecise(obj) {if (obj === null || typeof obj !== 'object') return obj;
const descriptors = Object.getOwnPropertyDescriptors(obj);
const clone = Object.create(Object.getPrototypeOf(obj), descriptors);
return clone;
}
const arr = [1, 2];
arr.custom = 'yes';
Object.defineProperty(arr, 'hidden', { value: 'no', enumerable: false });
const clonedArr = deepClonePrecise(arr);
console.log(clonedArr instanceof Array); // true
console.log(clonedArr.length); // 2(原型上的 length 属性被保留)
console.log(clonedArr.hidden); // 'no'(不可枚举属性仍存在)
console.log(Object.keys(clonedArr)); // ['0', '1', 'custom'](hidden 不出现,符合原行为)
注意边界:Symbol 键、循环引用、内置不可扩展对象
上述方法能处理 Symbol 键(getOwnPropertyDescriptors 包含 Symbol 属性),但无法自动深克隆嵌套对象的值 —— 它只做一层“属性结构”还原。若需真正深度克隆(如对象里还有对象),需递归调用自身,并对函数、Date、RegExp、Map、Set 等特殊类型单独处理。
- 循环引用会导致无限递归,需用 WeakMap 缓存已克隆对象作检测
-
Object.freeze()或Object.seal()后的对象,其描述符中configurable: false会被保留,但克隆后的新对象默认是可配置的,除非你手动设为false - 某些内置对象(如
Math、JSON)不可扩展,无法添加自有属性,克隆时会抛错,需提前判断跳过










