普通拷贝不保留冻结状态,因为object.freeze()仅作用于原对象本身,而所有拷贝操作均创建默认可变的新对象,必须手动再次冻结。

无法直接拷贝冻结对象并保持其冻结状态,因为 Object.freeze() 是浅冻结,且冻结后对象不可扩展、不可配置、不可写;而常规拷贝方法(如展开运算符、Object.assign、结构赋值)都会创建新对象——新对象默认是可变的,必须手动再次冻结。
为什么普通拷贝不保留冻结状态?
冻结(Object.freeze(obj))只作用于目标对象本身,不递归影响嵌套对象,也不影响拷贝行为。所有拷贝操作本质是「读取属性值 + 写入到新对象」,而新对象从创建起就是可扩展、可配置、可写的:
-
{...obj}或Object.assign({}, obj)→ 返回全新可变对象 -
JSON.parse(JSON.stringify(obj))→ 仅适用于纯数据,丢失函数、undefined、Symbol、循环引用,且结果必然可变 -
structuredClone(obj)→ 拷贝出的新对象也是可变的(规范明确要求)
正确做法:拷贝后立即冻结
先用任意方式深/浅拷贝,再对结果调用 Object.freeze()。若需浅拷贝冻结对象,推荐:
-
浅拷贝 + 冻结:
const copy = Object.freeze({ ...original }); -
兼容性写法:
const copy = Object.freeze(Object.assign({}, original)); -
带原型的拷贝(如需继承原对象原型):
const copy = Object.freeze(Object.assign(Object.create(Object.getPrototypeOf(original)), original));
处理嵌套冻结对象(深冻结)
如果原对象有冻结的子对象,仅浅冻结拷贝结果不足以保证深层不变性。此时需递归冻结:
function deepFreeze(obj) {
if (obj && typeof obj === 'object' && !Object.isFrozen(obj)) {
Object.getOwnPropertyNames(obj).forEach(prop => {
if (obj[prop] && typeof obj[prop] === 'object') {
deepFreeze(obj[prop]);
}
});
return Object.freeze(obj);
}
return obj;
}
const frozenCopy = deepFreeze(JSON.parse(JSON.stringify(original))); // 注意 JSON 局限性
// 或更安全地结合结构遍历(需自行处理 Date、RegExp 等)
注意边界情况
冻结对象拷贝时需留意:
-
不可枚举属性:
Object.assign和展开运算符只拷贝可枚举自有属性;要用Object.getOwnPropertyDescriptors+Object.defineProperties保留不可枚举或 accessor 属性 -
Symbol 键:展开运算符和
Object.assign会忽略 Symbol 键;需显式处理Object.getOwnPropertySymbols() - 冻结失败静默:严格模式下尝试修改冻结对象会抛错;非严格模式下静默失败,但拷贝过程本身不会报错
不复杂但容易忽略:冻结不是对象的“固有属性”,而是运行时状态;拷贝即新建,新建即默认可变——所以冻结永远是拷贝后的一步操作,没有捷径。











