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

瀏覽器相容性

另見