标准答案

  1. 可变对象可以在原引用上直接修改属性。不可变思路强调不直接改原对象,而是创建新对象表达变化。
  2. 前端状态管理里,不可变更新更容易做引用比较、变更追踪、撤销回滚和缓存判断,也能减少多个地方共享同一个对象时的意外修改。
  3. Object.preventExtensions 禁止新增属性,但已有属性仍然可以修改或删除。
  4. Object.seal 在 preventExtensions 的基础上禁止删除属性,并把已有属性设为不可配置。
  5. Object.freeze 再进一步把数据属性设为不可写,通常表示浅冻结。它不会自动冻结嵌套对象。

题目解析

不可变不是说所有数据都必须深拷贝。常见做法是只复制发生变化的路径,其它未变化分支继续共享引用,这样既能保留引用比较能力,也不会无意义复制整棵对象。

freeze、seal、preventExtensions 都是浅层限制。嵌套对象如果仍然是普通可变对象,仍然可以被修改。需要深冻结时要递归处理,但工程上更常见的是用不可变更新约定或状态管理工具。

严格模式下修改冻结对象的只读属性会报错;非严格模式下可能静默失败。面试里要能说出这类差异。

代码示例

下面示例展示不可变更新、浅冻结和三个对象限制 API 的差异。

JavaScript
const state = {
  user: {
    name: 'Alice',
    profile: { city: 'Hangzhou' }
  }
}

const nextState = {
  ...state,
  user: {
    ...state.user,
    name: 'Bob'
  }
}

nextState !== state // true
nextState.user !== state.user // true
nextState.user.profile === state.user.profile // true,未变分支共享引用

const frozen = Object.freeze({
  name: 'Alice',
  profile: { city: 'Hangzhou' }
})

frozen.profile.city = 'Shanghai' // 嵌套对象仍然可改

const sealed = Object.seal({ name: 'Alice' })
sealed.name = 'Bob'
// sealed.age = 18 // 不能新增
// delete sealed.name // 不能删除

const fixedShape = Object.preventExtensions({ name: 'Alice' })
fixedShape.name = 'Bob'
// fixedShape.age = 18 // 不能新增

常见误区

  • 误以为 Object.freeze 是深冻结。
  • 把不可变更新理解成必须深拷贝整个对象。
  • 忽略数组也是对象,也可以被冻结或被不可变更新。

作者信息