JavaScript Object.freeze() 详解

0 阅读3分钟

1. 核心概念

Object.freeze() 是 JavaScript 中的一个内置方法,用于使对象变为不可变(immutable)状态,防止其属性被修改、添加或删除。

1.1 基本语法

Object.freeze(obj)

2. 功能特性

2.1 主要作用

特性说明示例
阻止属性修改对象属性值不可更改obj.prop = 'new' 无效
阻止属性添加无法添加新属性obj.newProp = 'value' 无效
阻止属性删除无法删除属性delete obj.prop 无效
递归冻结浅层冻结,不递归冻结嵌套对象obj.nested.value 仍可修改

2.2 返回值

  • 返回原始对象本身(非副本)
  • 冻结后的对象仍然是同一个对象引用

3. 使用示例

3.1 基础用法

const person = {
  name: '张三',
  age: 25,
  city: '北京'
};

// 冻结对象
const frozenPerson = Object.freeze(person);

// 尝试修改属性 - 静默失败(严格模式下报错)
frozenPerson.name = '李四'; // 无效
frozenPerson.newProp = 'test'; // 无效
delete frozenPerson.age; // 无效

console.log(frozenPerson); // { name: '张三', age: 25, city: '北京' }

3.2 严格模式下的行为

'use strict';

const obj = { x: 42 };
Object.freeze(obj);

try {
  obj.x = 9; // TypeError: Cannot assign to read only property 'x' of object
} catch (e) {
  console.error(e.message);
}

3.3 浅层冻结特性

const config = {
  apiUrl: 'https://api.example.com',
  headers: {
    'Content-Type': 'application/json'
  }
};

Object.freeze(config);

// 顶层属性不可修改
config.apiUrl = 'https://new.api.com'; // 无效

// 但嵌套对象的内容仍可修改
config.headers['Content-Type'] = 'text/plain'; // 有效!
console.log(config.headers); // { 'Content-Type': 'text/plain' }

4. 相关方法对比

4.1 对象冻结方法对比

方法作用是否递归示例
Object.freeze()完全不可变否(浅层)Object.freeze(obj)
Object.seal()可修改现有属性,不可增删Object.seal(obj)
Object.preventExtensions()不可添加新属性,可修改/删除Object.preventExtensions(obj)

4.2 方法对比表

特性freeze()seal()preventExtensions()
修改属性值❌ 禁止✅ 允许✅ 允许
添加新属性❌ 禁止❌ 禁止❌ 禁止
删除属性❌ 禁止❌ 禁止✅ 允许
检查是否可扩展Object.isFrozen()Object.isSealed()Object.isExtensible()

5. 检测对象状态

5.1 检测方法

const obj = { a: 1 };

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

const sealedObj = { b: 2 };
Object.seal(sealedObj);
console.log(Object.isSealed(sealedObj)); // true

console.log(Object.isExtensible(obj)); // false

6. 实际应用场景

6.1 配置对象保护

// 应用配置
const APP_CONFIG = Object.freeze({
  API_BASE_URL: 'https://api.example.com',
  TIMEOUT: 5000,
  RETRY_COUNT: 3
});

// 防止意外修改
// APP_CONFIG.API_BASE_URL = 'hacked'; // 无效

6.2 常量定义

// 枚举值
const DIRECTION = Object.freeze({
  UP: 'UP',
  DOWN: 'DOWN',
  LEFT: 'LEFT',
  RIGHT: 'RIGHT'
});

// 使用
function move(direction) {
  if (!Object.values(DIRECTION).includes(direction)) {
    throw new Error('Invalid direction');
  }
  // ... 移动逻辑
}

6.3 函数参数保护

function processUser(user) {
  // 冻结传入的对象,防止函数内部意外修改
  const frozenUser = Object.freeze(user);
  
  // 使用 frozenUser 进行操作
  return {
    ...frozenUser,
    processed: true
  };
}

7. 注意事项

7.1 局限性

  1. 浅层冻结:只冻结对象本身,不递归冻结嵌套对象
  2. 无法冻结不可枚举属性:如 __proto__constructor
  3. 数组冻结:数组也是对象,同样适用
    const arr = [1, 2, 3];
    Object.freeze(arr);
    arr.push(4); // 无效
    arr[0] = 10; // 无效
    

7.2 性能考虑

  • 冻结操作本身有轻微性能开销
  • 对于大型对象,考虑是否需要完全冻结
  • 在频繁修改的对象上使用冻结会影响性能

8. 最佳实践

8.1 使用建议

  1. 顶层配置对象:使用 Object.freeze() 保护全局配置
  2. 枚举值定义:创建不可变的枚举对象
  3. 函数参数保护:防止函数内部意外修改传入对象
  4. 状态管理:在 Redux 等状态管理库中保护状态对象

8.2 替代方案

// 使用 Proxy 实现更精细的控制
const createImmutable = (obj) => {
  return new Proxy(obj, {
    set() {
      throw new Error('Object is immutable');
    },
    deleteProperty() {
      throw new Error('Object is immutable');
    }
  });
};

9. 总结

Object.freeze() 是 JavaScript 中实现对象不可变性的重要工具,适用于:

  • 保护配置对象不被意外修改
  • 定义常量枚举值
  • 在函数调用中保护参数对象
  • 实现不可变状态管理

关键要点

  • 冻结是浅层的,不递归冻结嵌套对象
  • 在严格模式下,修改冻结对象会抛出错误
  • 使用 Object.isFrozen() 可以检测对象是否被冻结
  • 对于需要深度冻结的场景,需要递归调用 Object.freeze()