深拷贝无法保留writable: false等属性描述符,因其本质是值迁移而非属性定义重建;需手动结合Object.getOwnPropertyDescriptors和Object.defineProperty实现。

JavaScript 深拷贝不会保留原始对象的 writable: false 属性描述符,这是由语言规范决定的——所有标准深拷贝方法(如 JSON.parse(JSON.stringify())、structuredClone()、Lodash 的 cloneDeep())都只复制属性值,不复制属性的元数据(如 writable、enumerable、configurable、get/set)。只读性在拷贝后默认丢失。
为什么只读属性在深拷贝中失效?
因为深拷贝本质是“值迁移”,而非“属性定义重建”:
-
JSON.stringify()只序列化可枚举的自有可序列化值,忽略所有属性特性;反序列化时用默认属性描述符(writable: true)创建新属性。 -
structuredClone()(ES2022+)虽支持更多类型(如 Map、Set、Date),但仍只克隆数据状态,不复制属性描述符或访问器。 - 手动递归实现(如遍历
Object.getOwnPropertyDescriptors())若未显式调用Object.defineProperty(),也无法保留writable: false。
如何保留只读属性?需手动重建描述符
若必须保持 writable: false,需结合 Object.getOwnPropertyDescriptors() 和 Object.defineProperty() 显式定义:
function deepCloneWithDescriptors(obj) {
if (obj === null || typeof obj !== 'object') return obj;
const cloned = Array.isArray(obj) ? [] : {};
// 先递归拷贝值(基础深拷贝)
for (const key in obj) {
if (Object.prototype.hasOwnProperty.call(obj, key)) {
cloned[key] = deepCloneWithDescriptors(obj[key]);
}
}
// 再还原属性描述符(包括 writable: false)
const descriptors = Object.getOwnPropertyDescriptors(obj);
for (const key in descriptors) {
Object.defineProperty(cloned, key, descriptors[key]);
}
return cloned;
}
// 示例
const source = {};
Object.defineProperty(source, 'id', {
value: 123,
writable: false,
enumerable: true,
configurable: false
});
const copy = deepCloneWithDescriptors(source);
console.log(Object.getOwnPropertyDescriptor(copy, 'id').writable); // false
注意事项与限制
该方案虽能保留只读性,但有实际约束:
- 不能处理循环引用(需额外检测和缓存机制);
- 无法复制不可枚举属性以外的隐藏状态(如私有字段
#field); - 对内置对象(如
Date、RegExp)需单独处理其特殊构造逻辑; - 若原对象含 getter/setter,拷贝后仍会执行原访问器逻辑,但新对象上的 setter 若被触发,可能因上下文不同而异常。
更稳妥的替代思路
与其强求拷贝时保留只读性,不如在设计阶段规避依赖运行时属性保护:
- 用
Object.freeze()冻结整个对象(比单个writable: false更彻底,且structuredClone()后仍可再次冻结); - 使用类封装 + 私有字段 + 只读 getter,把“只读语义”交由代码逻辑保障,而非属性描述符;
- 在关键路径做运行时校验(如 setter 中抛错),而非依赖底层属性特性。
Java免费学习笔记:立即使用
解锁 Java 大师之旅:从入门到精通的终极指南











