json方法和普通递归无法保留不可配置属性,因其仅复制值和可枚举结构,不获取或还原object.getownpropertydescriptors中的configurable等元特性;需显式用object.defineproperty重定义描述符才能维持不可配置性。

手动迭代无法强制深克隆“不可配置属性”(non-configurable properties),因为不可配置性是对象属性的元特性,属于 Object.defineProperty 的描述符约束,不是值本身;深克隆操作只复制值和结构,不改变目标对象的属性定义行为。
不可配置属性的本质限制
一个属性被设为 configurable: false 后,意味着:
- 不能通过
delete删除该属性 - 不能用
Object.defineProperty重新配置其configurable、writable或enumerable(除非writable为true且仅改writable) - 但不影响该属性的值是否可读、是否可被赋新值(只要 writable 允许)
深克隆关注的是“值的递归复制”,而非“在目标对象上复现原始 defineProperty 描述符”。因此,克隆后的对象中,该属性默认是可配置的——除非你显式用 Object.defineProperty 再次设置。
手动迭代时如何保留不可配置性(需主动还原)
若你希望克隆结果中某属性也保持 configurable: false,必须在迭代写入后,额外获取原始属性描述符并重定义:
- 用
Object.getOwnPropertyDescriptors(target)获取所有自有属性的完整描述符(含configurable、enumerable、writable、value或get/set) - 对每个描述符,先深克隆
value(或递归克隆get/set函数体内的引用值),再用Object.defineProperty(clone, key, descriptor)设置 - 注意:Symbol 键需用
Object.getOwnPropertySymbols()单独处理
示例关键逻辑:
function deepCloneWithDescriptors(obj) {
if (obj === null || typeof obj !== 'object') return obj;
const clone = Array.isArray(obj) ? [] : {};
// 复制数据属性 + 访问器属性
const descriptors = Object.getOwnPropertyDescriptors(obj);
const symbols = Object.getOwnPropertySymbols(obj);
for (const key in descriptors) {
const desc = descriptors[key];
if ('value' in desc) {
clone[key] = deepCloneWithDescriptors(desc.value); // 递归克隆值
} else if ('get' in desc) {
Object.defineProperty(clone, key, {
get: desc.get ? () => deepCloneWithDescriptors(desc.get.call(obj)) : undefined,
set: desc.set ? (v) => desc.set.call(clone, deepCloneWithDescriptors(v)) : undefined,
enumerable: desc.enumerable,
configurable: desc.configurable // ← 关键:显式保留 configurable
});
continue;
}
Object.defineProperty(clone, key, {
...desc,
value: clone[key],
configurable: desc.configurable, // ← 强制继承原 configurable 状态
writable: desc.writable
});
}
for (const sym of symbols) {
const desc = Object.getOwnPropertyDescriptor(obj, sym);
Object.defineProperty(clone, sym, {
...desc,
value: desc.value ? deepCloneWithDescriptors(desc.value) : undefined,
configurable: desc.configurable
});
}
return clone;
}
为什么 JSON 方法和普通递归都不行
常见深克隆方法默认忽略属性描述符:
-
JSON.parse(JSON.stringify()):直接丢弃所有不可枚举、访问器、Symbol、函数、undefined,更不保留configurable - 朴素 for-in / Object.keys 迭代:只能读取可枚举自有属性,跳过不可枚举属性(如
constructor)和访问器,且无法获知原始configurable状态 - 即使使用
WeakMap解决循环引用,也不自动捕获和还原描述符
实际建议
绝大多数业务场景不需要保留 configurable: false。如果你确实依赖该约束(例如封装内部状态的类实例),更合理的做法是:
- 不在克隆结果上强求相同描述符,而是通过设计保证语义安全(如用
Object.freeze冻结整个克隆对象) - 若必须镜像原始定义,优先使用
Object.getOwnPropertyDescriptors+Object.defineProperty组合,而非依赖“迭代赋值”这种隐式方式 - 注意:冻结(
Object.freeze)后的对象,所有属性自动变为configurable: false且writable: false,比逐个设描述符更简洁










