Object.seal()仅密封已存在对象的结构,不参与拷贝;需先拷贝再显式调用其密封副本,以确保字段名稳定但值可变,嵌套对象需单独处理。

Object.seal() 本身不参与拷贝,也不能直接“确保拷贝后的副本符合原始定义”。它只作用于**已存在的对象**,禁止添加/删除属性、将数据属性改为访问器属性(或反之),但允许修改已有数据属性的值。因此,要让拷贝后的业务副本满足“固定结构”,必须在拷贝完成后、使用前,**显式调用 Object.seal() 对副本执行密封**。
先拷贝,再密封:两步不可颠倒
密封操作不能提前到原始对象上(除非你本就不希望原始对象被修改);更不能指望深拷贝函数自动调用 seal。常见错误是误以为 JSON.parse(JSON.stringify(obj)) 或 structuredClone(obj) 会保留密封状态——它们不会,返回的总是新创建的、未密封的普通对象。
- 浅拷贝后需手动 seal:const copy = { ...original }; Object.seal(copy);
- 深拷贝后也需手动 seal:const copy = structuredClone(original); Object.seal(copy);
- 原始对象是否被 seal 无关紧要——关键是副本自身是否被 seal
密封 ≠ 冻结:允许改值,禁止动结构
业务中常需保持字段名稳定(如 id、status、createdAt 不可增删),但允许更新 status 或 createdAt。这正是 seal 的适用场景:它保留了运行时的可变性,又守住结构契约。
- ✅ 允许:copy.status = 'processed';
- ❌ 禁止:copy.priority = 'high';(添加新属性)
- ❌ 禁止:delete copy.id;(删除已有属性)
- ❌ 禁止:Object.defineProperty(copy, 'id', { get() { return 1; } });(重定义为 getter)
结合 TypeScript 类型 + seal 实现双重保障
TypeScript 编译期类型检查无法阻止运行时结构篡改。将 seal 作为初始化副本的必经步骤,能弥补这一缺口:
- 定义明确接口:interface Order { id: string; status: 'pending' | 'shipped'; }
- 创建副本并立即 seal:const orderCopy: Order = Object.seal({ ...originalOrder });
- 后续任何违反结构的操作(如 orderCopy.userId = 123)在开发环境会静默失败(非严格模式)或抛出 TypeError(严格模式),便于早期暴露问题
注意边界:seal 不递归,嵌套对象需单独处理
如果业务对象含嵌套结构(如 user.profile),Object.seal() 只密封顶层,profile 对象仍可自由增删属性。需按需递归 seal 或仅 seal 关键层级:
- 仅密封顶层(常用):Object.seal(copy); // profile 本身仍可修改
- 递归 seal(谨慎使用):function deepSeal(obj) { if (obj && typeof obj === 'object') { Object.seal(obj); for (const val of Object.values(obj)) deepSeal(val); } return obj; }
- 更推荐:对关键嵌套子对象显式 seal,例如 Object.seal(copy.user);










