如何用 Object.getOwnPropertyDescriptors 完美克隆包含 Getter/Setter 的复杂对象 Object.getOwnPropertyDescriptors 为什么能拿到 getter/setter 这里有个常见的误解:很多人以为 Object.assign 或者

这里有个常见的误解:很多人以为 Object.assign 或者展开运算符({...obj})能复制一切。其实不然,它们只复制属性的「值」。对于 get 和 set 这类访问器函数,它们直接视而不见——这些函数根本不会被遍历到。
长期稳定更新的攒劲资源: >>>点此立即查看<<<
那么,关键在哪里?答案是 Object.getOwnPropertyDescriptors。它返回的不是值,而是完整的属性描述符对象。这个描述符里包含了 get、set、enumerable、configurable 等所有元信息。可以说,这才是还原访问器属性的唯一可靠入口。
如果用了 Object.assign,会发生什么?它会将 getter 当作普通函数调用一次,然后把得到的返回值,作为一个静态值赋给目标属性。至于 setter,更是直接丢失了。这显然不是我们想要的克隆。
正确的做法,是把 Object.getOwnPropertyDescriptors 拿到的描述符,原封不动地交给 Object.defineProperties 来“复刻”:
const source = {
_x: 10,
get x() { return this._x * 2; },
set x(v) { this._x = v / 2; }
};
const descriptors = Object.getOwnPropertyDescriptors(source);
const clone = Object.defineProperties({}, descriptors);
// clone.x 是响应式的,修改 clone.x 会触发 setter
// 用 Object.assign({}, source) 得到的是 { x: 20 } —— 静态值,无访问器
需要明确一点:Object.getOwnPropertyDescriptors 只作用于对象自身的属性。它不包含从原型链继承来的 getter/setter,也自然不处理对象内部的嵌套结构。所以,要实现深克隆,就得手动递归。
具体思路可以这么走:
Object.getOwnPropertyDescriptors 获取其自身的所有描述符。value 字段进行判断:如果这个值本身又是对象或数组,那就需要递归克隆。get 或 set 字段,保持原函数引用即可(函数体不能、也不应该被深克隆)。defineProperties 只管定义属性,不会自动设置对象的原型。如果需要克隆对象继承的原型链,得额外用 Object.setPrototypeOf(clone, Object.getPrototypeOf(source)) 来补上。理论清楚了,但一写代码,下面这几个坑几乎人人都会踩到:
enumerable(可枚举)和 configurable(可配置)默认是 false。如果源属性是 true 但克隆时没传过去,克隆后的属性可能就变得不可枚举,或者不可删除了。Object.getOwnPropertyDescriptors 只处理字符串键的属性。对于 Symbol 键的属性,需要用 Object.getOwnPropertySymbols 单独获取并补全到克隆流程中。get 而没有 set(即一个只读的访问器属性),克隆后理应保持只读。但如果不小心把 set: undefined 传给了 defineProperties,引擎会静默地将其转换为一个普通的数据属性,从而变得可写。说到底,所谓“完美”克隆,并不是依赖某一个神奇的 API 就能搞定。它考验的是对属性描述符完整结构的理解,以及在调用 defineProperties 时,是否严格保留了每个字段的原始语义。否则,克隆出来的对象看着像,一旦修改值或者用 for...in 遍历,马脚就露出来了。
侠游戏发布此文仅为了传递信息,不代表侠游戏网站认同其观点或证实其描述