Object.freeze

JavaScript

在 JavaScript 中,如果使用 const 定义变量,那么变量就无法再被修改。但是这种“不可变”仅限于值类型,对于引用类型来说,不可变的只有引用指针本身,其指向的内容还是可变的。

比如:

const obj = { a: 1 };

// works
obj.a = 2;

如果希望定义的对象里面的值也不可以被修改,可以使用 Object.freeze API。如:

const obj = Object.freeze({ a: 1 });

// throw error in strict mode
obj.a = 2;

事实上,Object.freeze 处理过的对象,不仅不能修改已有的数据(包括值、可枚举性、可配置性、可写性),也不能添加和删除字段。

注:如果只是单纯的希望不能增加/删除字段,但是依然可以改变对象的值,可以考虑使用 Object.seal API。

几点 Object.freeze 值得注意的细节:

  1. Object.freeze 执行后如果试图改变对象,在 strict mode 下会报错,在非 strict mode 下不会报错,但改动行为同样不会执行:
const obj = Object.freeze({ a: 1 });

// 在非 strict mode 下,下面的语句不会报错
obj.a = 2;

console.log(obj.a); // => 1
  1. Object.freeze 返回的其实就是原来的对象。因此,即使不重新赋值,原来的对象也已经变成不可变的了:
const obj = { a: 1 };
Object.freeze(obj);

// not work
obj.a = 2;
  1. 如果在 Object.freeze 之前定义了 setter,那么在 Object.freeze 之后依然是可以其效果的。但是 Object.freeze 之后就不能再定义新的 setter 了。举例来说:
(function () {
  'use strict';
  const obj = { a: 0 };
  let val = 'value';
  Object.defineProperty(obj, 'key', {
    get() {
      return val;
    },
    set(value) {
      console.log('setter triggered with value: ', value);
      val = value;
    },
  });
  Object.freeze(obj);
  // throw error:
  // obj.a = 1;

  // not throw error:
  obj.key = 'value';

  // throw error:
  Object.defineProperty(obj, 'b', {
    get() { return 1; },
    set() { },
  })
}());
  1. Object.freeze API 不会递归对 Object 内的属性进行“冻结”操作,即:
const obj = Object.freeze({ deeper: { a: 1 } });
obj.deeper.a = 2;

console.log(obj.deeper.a); // => 2

如果希望将整个对象都进行冻结,就需要递归对所有的属性进行 Object.freeze 操作:

function deepFreeze(obj) {
  for (const name of Object.getOwnPropertyNames(obj)) {
    const value = obj[name];
    if (value && typeof value === 'object') {
      deepFreeze(obj);
    }
  }
  return Object.freeze(obj);
}
  1. 在 ES2015 中,如果 Object.freeze 传入的参数是值类型/null/undefined,会直接返回;在 ES5 中传入则会报错。