实现Object.freeze

手写实现Object.freeze方法,冻结对象使其不可修改

问题

Object.freeze() 是 JavaScript 中用于冻结对象的方法,冻结后的对象不能添加新属性、删除现有属性、修改属性值,也不能修改属性的可枚举性、可配置性、可写性。本题要求手写实现一个类似的功能。

需要注意的是,Object.freeze() 是浅冻结,只冻结对象的第一层属性,嵌套对象不会被冻结。

解答

/**
 * 实现 Object.freeze 方法
 * @param {Object} obj - 需要冻结的对象
 * @returns {Object} - 返回冻结后的对象
 */
function myFreeze(obj) {
  // 如果不是对象或为 null,直接返回
  if (obj === null || typeof obj !== 'object') {
    return obj;
  }

  // 获取对象的所有自有属性名(包括不可枚举属性)
  const props = Object.getOwnPropertyNames(obj);

  // 遍历所有属性,将其设置为不可写、不可配置
  props.forEach(prop => {
    const descriptor = Object.getOwnPropertyDescriptor(obj, prop);
    
    // 如果是数据属性,设置为不可写
    if (descriptor && descriptor.configurable) {
      Object.defineProperty(obj, prop, {
        writable: false,      // 不可写
        configurable: false   // 不可配置(不可删除)
      });
    }
  });

  // 阻止对象扩展(不能添加新属性)
  Object.preventExtensions(obj);

  return obj;
}

/**
 * 深度冻结对象(递归冻结所有嵌套对象)
 * @param {Object} obj - 需要深度冻结的对象
 * @returns {Object} - 返回深度冻结后的对象
 */
function deepFreeze(obj) {
  // 先冻结当前对象
  myFreeze(obj);

  // 递归冻结所有属性值
  Object.getOwnPropertyNames(obj).forEach(prop => {
    const value = obj[prop];
    // 如果属性值是对象且不为 null,递归冻结
    if (value !== null && typeof value === 'object') {
      deepFreeze(value);
    }
  });

  return obj;
}

使用示例

// 示例1:基本使用
const obj1 = {
  name: 'Alice',
  age: 25
};

myFreeze(obj1);

obj1.name = 'Bob';        // 修改无效
obj1.gender = 'female';   // 添加无效
delete obj1.age;          // 删除无效

console.log(obj1);        // { name: 'Alice', age: 25 }

// 示例2:浅冻结的局限性
const obj2 = {
  user: {
    name: 'Tom',
    age: 30
  }
};

myFreeze(obj2);

obj2.user = {};                    // 修改无效
obj2.user.name = 'Jerry';          // 修改成功(嵌套对象未被冻结)

console.log(obj2.user.name);       // 'Jerry'

// 示例3:深度冻结
const obj3 = {
  user: {
    name: 'Tom',
    address: {
      city: 'Beijing'
    }
  }
};

deepFreeze(obj3);

obj3.user.name = 'Jerry';                  // 修改无效
obj3.user.address.city = 'Shanghai';       // 修改无效

console.log(obj3.user.name);               // 'Tom'
console.log(obj3.user.address.city);       // 'Beijing'

// 示例4:检测对象是否被冻结
const obj4 = { x: 1 };
myFreeze(obj4);

console.log(Object.isFrozen(obj4));        // true

// 示例5:冻结数组
const arr = [1, 2, 3];
myFreeze(arr);

arr.push(4);              // 添加无效(严格模式下会报错)
arr[0] = 10;              // 修改无效

console.log(arr);         // [1, 2, 3]

关键点

  • 三个操作

    1. 使用 Object.defineProperty 将所有属性设置为不可写(writable: false
    2. 将所有属性设置为不可配置(configurable: false),防止删除和重新定义
    3. 使用 Object.preventExtensions 阻止添加新属性
  • 获取所有属性:使用 Object.getOwnPropertyNames() 获取所有自有属性(包括不可枚举属性),而不是 Object.keys()

  • 浅冻结 vs 深冻结:原生 Object.freeze() 只是浅冻结,嵌套对象不会被冻结。如需深度冻结,需要递归处理所有嵌套对象

  • 类型检查:在冻结前需要检查参数是否为对象,非对象类型直接返回

  • 返回值Object.freeze() 返回被冻结的对象本身,而不是创建新对象

  • 严格模式:在严格模式下,尝试修改冻结对象会抛出 TypeError 错误;非严格模式下会静默失败

  • 性能考虑:冻结对象会影响性能,特别是深度冻结大型对象时,应谨慎使用