Object.freeze()

Baseline 已广泛支持

此特性已相当成熟,可在许多设备和浏览器版本上使用。自 ⁨2015 年 7 月⁩以来,各浏览器均已提供此特性。

Object.freeze() 静态方法会冻结一个对象。冻结一个对象 阻止扩展,并使现有属性变为不可写和不可配置。一个被冻结的对象将不再能被修改:不能添加新属性,不能删除现有属性,它们的枚举性、可配置性、可写性或值都不能被改变,也不能重新分配对象的原型。freeze() 返回的是传入的同一个对象。

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

试一试

const obj = {
  prop: 42,
};

Object.freeze(obj);

obj.prop = 33;
// Throws an error in strict mode

console.log(obj.prop);
// Expected output: 42

语法

js
Object.freeze(obj)

参数

obj

要冻结的对象。

返回值

传递给函数的对象。

描述

冻结一个对象相当于 阻止扩展,然后将所有现有 属性描述符configurable 设置为 false,对于数据属性,同时也将 writable 设置为 false。被冻结对象的属性集既不能添加也不能删除。任何尝试这样做的操作都会失败,可能会静默失败,或者抛出一个 TypeError 异常(最常见,但并非仅限于在 严格模式 下)。

对于被冻结对象的数据属性,由于 writableconfigurable 属性都被设置为 false,因此其值无法被修改。访问器属性(getter 和 setter)的工作方式相同——getter 返回的属性值可能仍然会改变,并且在设置属性时,调用 setter 不会抛出错误。请注意,值本身是对象(如嵌套对象)的内容仍然可以被修改,除非它们也被冻结了。作为对象,数组也可以被冻结;冻结后,其元素不能被修改,也不能向数组添加或删除元素。

私有元素 不是属性,也没有属性描述符的概念。冻结带有私有元素的对象的冻结操作不会阻止这些私有元素的值被更改。(冻结对象通常是作为一种安全措施来防止外部代码篡改,但外部代码本来也无法访问私有元素。)无论对象是否被冻结,都无法向对象添加或删除私有元素。

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

具有元素的 TypedArrayDataView 会导致 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 = "value";

obj1.internal.a; // 'value'

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

深度冻结

调用 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 语法创建的函数具有一个带有 constructor 属性的 prototype 属性,该属性指向函数本身,因此它们默认存在循环。其他函数,例如 箭头函数,仍然可以被冻结。

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® 2026 语言规范
# sec-object.freeze

浏览器兼容性

另见