Object.freeze()

Object.freeze() 静态方法冻结一个对象。冻结一个对象会阻止扩展并使现有属性不可写且不可配置。冻结的对象无法再更改:无法添加新属性,无法删除现有属性,无法更改其可枚举性、可配置性、可写性或值,并且无法重新分配对象的原型。freeze() 返回传入的相同对象。

冻结对象是 JavaScript 提供的最高完整性级别。

试一试

语法

js
Object.freeze(obj)

参数

obj

要冻结的对象。

返回值

传递给函数的对象。

描述

冻结对象等效于阻止扩展,然后将所有现有属性的描述符configurable更改为false——对于数据属性,也将writable更改为false。无法向冻结对象的属性集添加或从中删除任何内容。任何尝试这样做都会失败,要么静默失败,要么抛出TypeError异常(最常见,但并非唯一,在严格模式下)。

对于冻结对象的 数据属性,由于writableconfigurable属性设置为false,因此无法更改其值。访问器属性(getter 和 setter)的工作方式相同——getter 返回的属性值可能仍然会更改,并且在设置属性时,调用 setter 仍然不会抛出错误。请注意,除非对象也被冻结,否则对象的值仍然可以修改。作为对象,数组可以被冻结;这样做后,其元素将无法更改,并且无法向数组添加或从中删除元素。

私有属性没有属性描述符的概念。冻结包含私有属性的对象不会阻止这些私有属性的值被更改。(冻结对象通常是为了防止外部代码进行安全措施,但外部代码无论如何都无法访问私有属性。)无论对象是否冻结,都无法向对象添加或从中删除私有属性。

freeze() 返回传入函数的相同对象。它不会创建冻结的副本。

TypedArray或具有元素的DataView将导致TypeError,因为它们是内存上的视图,并且肯定会导致其他可能的问题

js
Object.freeze(new Uint8Array(0)); // No elements
// Uint8Array []

Object.freeze(new Uint8Array(1)); // Has elements
// TypeError: Cannot freeze array buffer views with elements

Object.freeze(new DataView(new ArrayBuffer(32))); // No elements
// DataView {}

Object.freeze(new Float64Array(new ArrayBuffer(64), 63, 0)); // No elements
// Float64Array []

Object.freeze(new Float64Array(new ArrayBuffer(64), 32, 2)); // Has elements
// TypeError: Cannot freeze array buffer views with elements

请注意,由于标准的三个属性(buf.byteLengthbuf.byteOffsetbuf.buffer)是只读的(ArrayBufferSharedArrayBuffer也是如此),因此没有理由尝试冻结这些属性。

Object.seal()不同,使用Object.freeze()冻结的对象中的现有属性将变为不可变,并且数据属性无法重新赋值。

示例

冻结对象

js
const obj = {
  prop() {},
  foo: "bar",
};

// Before freezing: new properties may be added,
// and existing properties may be changed or removed
obj.foo = "baz";
obj.lumpy = "woof";
delete obj.prop;

// Freeze.
const o = Object.freeze(obj);

// The return value is just the same object we passed in.
o === obj; // true

// The object has become frozen.
Object.isFrozen(obj); // === true

// Now any changes will fail
obj.foo = "quux"; // silently does nothing
// silently doesn't add the property
obj.quaxxor = "the friendly duck";

// In strict mode such attempts will throw TypeErrors
function fail() {
  "use strict";
  obj.foo = "sparky"; // throws a TypeError
  delete obj.foo; // throws a TypeError
  delete obj.quaxxor; // returns true since attribute 'quaxxor' was never added
  obj.sparky = "arf"; // throws a TypeError
}

fail();

// Attempted changes through Object.defineProperty;
// both statements below throw a TypeError.
Object.defineProperty(obj, "ohai", { value: 17 });
Object.defineProperty(obj, "foo", { value: "eit" });

// It's also impossible to change the prototype
// both statements below will throw a TypeError.
Object.setPrototypeOf(obj, { x: 20 });
obj.__proto__ = { x: 20 };

冻结数组

js
const a = [0];
Object.freeze(a); // The array cannot be modified now.

a[0] = 1; // fails silently

// In strict mode such attempt will throw a TypeError
function fail() {
  "use strict";
  a[0] = 1;
}

fail();

// Attempted to push
a.push(2); // throws a TypeError

被冻结的对象是不可变的。但是,它不一定是常量。以下示例显示冻结的对象不是常量(冻结是浅层的)。

js
const obj1 = {
  internal: {},
};

Object.freeze(obj1);
obj1.internal.a = "aValue";

obj1.internal.a; // 'aValue'

要成为一个常量对象,整个引用图(对其他对象的直接和间接引用)必须仅引用不可变的冻结对象。被冻结的对象被称为不可变的,因为整个对象状态(值和对其他对象的引用)在整个对象中都是固定的。请注意,字符串、数字和布尔值始终是不可变的,并且函数和数组是对象。

深度冻结

调用Object.freeze(object)的结果仅适用于object本身的直接属性,并且只会阻止object上进行将来的属性添加、删除或值重新赋值操作。如果这些属性的值本身是对象,则这些对象不会被冻结,并且可能是属性添加、删除或值重新赋值操作的目标。

js
const employee = {
  name: "Mayank",
  designation: "Developer",
  address: {
    street: "Rohini",
    city: "Delhi",
  },
};

Object.freeze(employee);

employee.name = "Dummy"; // fails silently in non-strict mode
employee.address.city = "Noida"; // attributes of child object can be modified

console.log(employee.address.city); // "Noida"

要使对象不可变,请递归冻结每个非基本类型属性(深度冻结)。当您知道对象在引用图中不包含任何循环时,请根据您的设计在逐案的基础上使用此模式,否则将触发无限循环。例如,使用function语法创建的函数具有一个prototype属性,该属性具有一个指向函数本身的constructor属性,因此它们默认具有循环。其他函数,例如箭头函数,仍然可以被冻结。

deepFreeze()的增强是存储它已经访问过的对象,以便在对象正在变得不可变的过程中,您可以抑制递归调用deepFreeze()。例如,请参阅使用WeakSet检测循环引用。您仍然存在冻结不应该冻结的对象的风险,例如window

js
function deepFreeze(object) {
  // Retrieve the property names defined on object
  const propNames = Reflect.ownKeys(object);

  // Freeze properties before freezing self
  for (const name of propNames) {
    const value = object[name];

    if ((value && typeof value === "object") || typeof value === "function") {
      deepFreeze(value);
    }
  }

  return Object.freeze(object);
}

const obj2 = {
  internal: {
    a: null,
  },
};

deepFreeze(obj2);

obj2.internal.a = "anotherValue"; // fails silently in non-strict mode
obj2.internal.a; // null

规范

规范
ECMAScript 语言规范
# sec-object.freeze

浏览器兼容性

BCD 表格仅在启用 JavaScript 的浏览器中加载。

另请参阅