JavaScript 中的 Object.freeze() 及其和 TypeScript 中 readonly 的关系

9,622 阅读3分钟

一、简介

Object.freeze() 是 JavaScript 提供的一个内置方法,用于冻结一个对象。冻结后的对象将变成**不可更改(immutable)**的:不能添加、删除或修改其属性。这个方法在需要创建只读配置对象、保护数据结构时非常有用。

const obj = { a: 1 };
Object.freeze(obj);
obj.a = 2;         // 无效
obj.b = 3;         // 无效
delete obj.a;      // 无效
console.log(obj);  // { a: 1 }

二、基本用法

语法:

Object.freeze(obj);
  • obj:要冻结的对象。
  • 返回值:冻结后的对象(与传入对象是同一个引用)。

示例:

const config = {
  debug: true,
  version: "1.0.0"
};

Object.freeze(config);

config.debug = false;         // 不生效
config.newProp = "new";       // 不生效
delete config.version;        // 不生效

三、冻结的实现机制

Object.freeze() 本质上是通过内部的 [[Configurable]] 和 [[Writable]] 属性控制对象的行为:

  1. 所有属性都变成不可写(writable: false)
  2. 所有属性都变成不可配置(configurable: false)
  3. 不能添加新属性
  4. 不能删除已有属性

冻结对象后,其行为相当于执行了:

Object.defineProperty(obj, 'prop', {
  writable: false,
  configurable: false
});

⚠️ 注意:冻结仅限于“第一层属性”,即 浅冻结(shallow freeze)

深冻结(deep freeze)示例:

function deepFreeze(obj) {
  Object.freeze(obj);
  for (const key in obj) {
    if (typeof obj[key] === 'object' && obj[key] !== null && !Object.isFrozen(obj[key])) {
      deepFreeze(obj[key]);
    }
  }
  return obj;
}

四、Object.freeze() 与 TypeScript 中 readonly 的关系

TypeScript 提供的 readonly 关键字也能使对象属性只读,但它和 Object.freeze() 有本质区别:

特性readonly(TypeScript)Object.freeze()(JavaScript)
作用时机编译期类型检查运行时行为
是否影响运行时对象结构❌ 否✅ 是
是否可以递归Readonly<T> 泛型支持递归❌ 默认浅冻结
是否可以取消✅ 可重新赋值绕过限制❌ 不可逆
是否安全⚠️ 只能防止开发者在 TypeScript 中误改✅ 能防止运行时代码篡改

示例:readonly 类型检查(只影响编译时)

type Config = {
  readonly debug: boolean;
};

const cfg: Config = { debug: true };
cfg.debug = false;  // ❌ TypeScript 报错(但运行时仍然可修改)

编译后的 JavaScript 是什么样的?

const cfg: { readonly debug: boolean } = { debug: true };

会被编译为:

const cfg = { debug: true };

也就是说,readonly 不会生成任何运行时代码,只是类型约束。

什么时候用 readonly,什么时候用 freeze

  • 使用 readonly
    • 开发阶段防止误用(类型安全)
    • 需要在类型系统中表达不变性,特别是在函数参数、接口定义中
  • 使用 Object.freeze()
    • 运行时防止对象被篡改,保障数据一致性
    • 尤其适用于运行环境中存在非 TypeScript 代码的场景,或需要跨模块/框架传递配置

建议:

如果你使用 TypeScript,可以同时使用 readonly + Object.freeze(),提供开发阶段和运行时的双重保障。

const cfg = Object.freeze({
  debug: true as const,
  mode: "dev" as const,
});

五、使用注意事项

  1. 只能冻结对象类型(Object, Array, Function)
  2. 浅冻结:子对象依然可以被修改,如数组中的对象、嵌套属性等。
  3. 无法解冻:没有 Object.unfreeze(),一旦冻结不可撤销(只能手动创建副本)。
  4. 严格模式下会抛出错误:对冻结对象修改会在严格模式中抛出 TypeError。
  5. 性能影响:冻结对象可能对性能有一定影响,尤其在深度冻结时应谨慎使用。

六、常见用途

  • 创建不可变配置对象:

    const CONFIG = Object.freeze({
      host: "localhost",
      port: 8080
    });
    
  • 防止函数参数被修改:

    function process(options) {
      Object.freeze(options);
      // 确保 options 不被更改
    }
    
  • 状态管理中保持数据不可变性(比如 Redux 状态树)

七、如何检测对象是否被冻结

使用 Object.isFrozen() 来检测一个对象是否被冻结:

const obj = {};
console.log(Object.isFrozen(obj)); // false

Object.freeze(obj);
console.log(Object.isFrozen(obj)); // true

八、小结

特性描述
是否可修改属性值❌ 否
是否可添加新属性❌ 否
是否可删除属性❌ 否
是否递归冻结❌ 否(默认)
是否可检测冻结状态Object.isFrozen()
是否可撤销冻结❌ 否