Object.defineProperty()

Baseline 已廣泛支援

此特性已相當成熟,可在許多裝置和瀏覽器版本上使用。自 ⁨2015 年 7 月⁩以來,各瀏覽器均已提供此特性。

Object.defineProperty() 靜態方法會直接在一個物件上定義一個新屬性,或修改其現有屬性,並返回此物件。

試一試

const object = {};

Object.defineProperty(object, "foo", {
  value: 42,
  writable: false,
});

object.foo = 77;
// Throws an error in strict mode

console.log(object.foo);
// Expected output: 42

語法

js
Object.defineProperty(obj, prop, descriptor)

引數

obj

要定義屬性的物件。

prop

一個字串或 Symbol,指定了要定義或修改的屬性的鍵。

描述符(descriptor)

要定義或修改的屬性的描述符。

返回值

傳入函式的物件,其指定屬性已被新增或修改。

描述

Object.defineProperty() 允許精確地新增或修改物件上的屬性。透過賦值操作新增的普通屬性,會出現在屬性列舉(for...inObject.keys() 等)中,它們的值可以被更改,也可以被刪除。該方法允許改變這些額外的細節,使其與預設值不同。預設情況下,使用 Object.defineProperty() 新增的屬性是不可寫、不可列舉和不可配置的。此外,Object.defineProperty() 使用的是 [[DefineOwnProperty]] 內部方法,而不是 [[Set]],所以即使屬性已經存在,它也不會呼叫 setter

物件裡目前存在的屬性描述符有兩種主要形式:資料描述符和存取器描述符。資料描述符是一個具有值的屬性,該值可能是可寫的,也可能不是可寫的。存取器描述符是由 getter/setter 函式對描述的屬性。一個描述符只能是這兩者之一;不能同時是兩者。

資料描述符和存取器描述符都是物件。它們共享以下可選鍵(請注意:此處提到的預設值是在使用 Object.defineProperty() 定義屬性時的情況):

可配置

當該值為 false 時:

  • 該屬性的型別不能在資料屬性和存取器屬性之間更改;
  • 該屬性不可被刪除;
  • 該屬性描述符的其他特性也不能被更改(但是,如果它是一個 writable: true 的資料描述符,則 value 可以被更改,writable 也可以被更改為 false)。

預設為 false

可列舉

當且僅當在列舉相應物件上的屬性時,該屬性才會出現。預設為 false

資料描述符還具有以下可選鍵:

value

與屬性關聯的值。可以是任何有效的 JavaScript 值(數字、物件、函式等)。預設為 undefined

可寫

如果與屬性關聯的值可以使用賦值運算子更改,則為 true預設為 false

存取器描述符還具有以下可選鍵:

get

一個用作屬性 getter 的函式,如果沒有 getter 則為 undefined。當訪問該屬性時,會呼叫此函式,不帶引數,並將 this 設定為透過其訪問屬性的物件(由於繼承,這可能不是定義該屬性的物件)。返回值將用作屬性的值。預設為 undefined

set

一個用作屬性 setter 的函式,如果沒有 setter 則為 undefined。當屬性被賦值時,會呼叫此函式,帶一個引數(要賦給屬性的值),並將 this 設定為透過其分配屬性的物件。預設為 undefined

如果一個描述符不具有 valuewritablegetset 中的任何一個鍵,那麼它將被視為一個數據描述符。如果一個描述符既是資料描述符(因為它有 valuewritable),又是存取器描述符(因為它有 getset),則會丟擲異常。

這些特性不一定是描述符自身的屬性。繼承的屬性也會被考慮。為了確保保留這些預設值,你可以預先凍結描述符物件原型鏈中的現有物件,顯式指定所有選項,或者建立一個null 原型物件

js
const obj = {};
// 1. Using a null prototype: no inherited properties
const descriptor = Object.create(null);
descriptor.value = "static";

// not enumerable, not configurable, not writable as defaults
Object.defineProperty(obj, "key", descriptor);

// 2. Being explicit by using a throw-away object literal with all attributes present
Object.defineProperty(obj, "key2", {
  enumerable: false,
  configurable: false,
  writable: false,
  value: "static",
});

// 3. Prevents adding or removing the object prototype properties
// (value, get, set, enumerable, writable, configurable)
Object.freeze(Object.prototype);

當屬性已存在時,Object.defineProperty() 會嘗試根據描述符中的值以及該屬性當前的配置來修改該屬性。

如果舊描述符的 configurable 特性設定為 false,則該屬性被稱為不可配置的。此時,無法更改不可配置的存取器屬性的任何特性,也無法在資料屬性和存取器屬性型別之間切換。對於 writable: true 的資料屬性,可以修改其值,並將 writable 特性從 true 更改為 false。當嘗試更改不可配置屬性的特性時(除了允許的 valuewritable),會丟擲 TypeError 異常,除非是在資料屬性上定義與原始值相同的值。

噹噹前屬性是可配置的時,將一個特性定義為 undefined 會有效地刪除它。例如,如果 o.k 是一個存取器屬性,Object.defineProperty(o, "k", { set: undefined }) 將會移除 setter,使 k 只剩下 getter 並變為只讀。如果新描述符中缺少某個特性,則保留舊描述符中該特性的值(它不會被隱式地重新定義為 undefined)。透過提供一個不同“型別”的描述符,可以在資料屬性和存取器屬性之間切換。例如,如果新描述符是資料描述符(帶有 valuewritable),原始描述符的 getset 特性都將被丟棄。

示例

建立一個屬性

當物件中不存在指定的屬性時,Object.defineProperty() 會按照描述建立一個新屬性。描述符中可以省略欄位,這些欄位的預設值會被填入。

js
const o = {}; // Creates a new object

// Example of an object property added
// with defineProperty with a data property descriptor
Object.defineProperty(o, "a", {
  value: 37,
  writable: true,
  enumerable: true,
  configurable: true,
});
// 'a' property exists in the o object and its value is 37

// Example of an object property added
// with defineProperty with an accessor property descriptor
let bValue = 38;
Object.defineProperty(o, "b", {
  get() {
    return bValue;
  },
  set(newValue) {
    bValue = newValue;
  },
  enumerable: true,
  configurable: true,
});
o.b; // 38
// 'b' property exists in the o object and its value is 38
// The value of o.b is now always identical to bValue,
// unless o.b is redefined

// You cannot try to mix both:
Object.defineProperty(o, "conflict", {
  value: 0x9f91102,
  get() {
    return 0xdeadbeef;
  },
});
// throws a TypeError: value appears
// only in data descriptors,
// get appears only in accessor descriptors

修改一個屬性

當修改現有屬性時,當前屬性的配置決定了該操作是成功、不執行任何操作,還是丟擲 TypeError

Writable 特性

writable 屬性特性為 false 時,該屬性被稱為“不可寫的”。它不能被重新賦值。嘗試寫入一個不可寫的屬性不會改變它,並且在嚴格模式下會導致錯誤。

js
const o = {}; // Creates a new object

Object.defineProperty(o, "a", {
  value: 37,
  writable: false,
});

console.log(o.a); // 37
o.a = 25; // No error thrown
// (it would throw in strict mode,
// even if the value had been the same)
console.log(o.a); // 37; the assignment didn't work

// strict mode
(() => {
  "use strict";
  const o = {};
  Object.defineProperty(o, "b", {
    value: 2,
    writable: false,
  });
  o.b = 3; // throws TypeError: "b" is read-only
  return o.b; // returns 2 without the line above
})();

Enumerable 特性

enumerable 屬性特性定義了該屬性是否會被 Object.assign()展開運算子考慮。對於非 Symbol 屬性,它還定義了該屬性是否會出現在 for...in 迴圈和 Object.keys() 中。更多資訊,請參閱屬性的可列舉性和所有權

js
const o = {};
Object.defineProperty(o, "a", {
  value: 1,
  enumerable: true,
});
Object.defineProperty(o, "b", {
  value: 2,
  enumerable: false,
});
Object.defineProperty(o, "c", {
  value: 3,
}); // enumerable defaults to false
o.d = 4; // enumerable defaults to true when creating a property by setting it
Object.defineProperty(o, Symbol.for("e"), {
  value: 5,
  enumerable: true,
});
Object.defineProperty(o, Symbol.for("f"), {
  value: 6,
  enumerable: false,
});

for (const i in o) {
  console.log(i);
}
// Logs 'a' and 'd' (always in that order)

Object.keys(o); // ['a', 'd']

o.propertyIsEnumerable("a"); // true
o.propertyIsEnumerable("b"); // false
o.propertyIsEnumerable("c"); // false
o.propertyIsEnumerable("d"); // true
o.propertyIsEnumerable(Symbol.for("e")); // true
o.propertyIsEnumerable(Symbol.for("f")); // false

const p = { ...o };
p.a; // 1
p.b; // undefined
p.c; // undefined
p.d; // 4
p[Symbol.for("e")]; // 5
p[Symbol.for("f")]; // undefined

Configurable 特性

configurable 特性控制了屬性是否可以從物件中刪除,以及它的特性(valuewritable 除外)是否可以被更改。

這個例子說明了一個不可配置的存取器屬性。

js
const o = {};
Object.defineProperty(o, "a", {
  get() {
    return 1;
  },
  configurable: false,
});

Object.defineProperty(o, "a", {
  configurable: true,
}); // throws a TypeError
Object.defineProperty(o, "a", {
  enumerable: true,
}); // throws a TypeError
Object.defineProperty(o, "a", {
  set() {},
}); // throws a TypeError (set was undefined previously)
Object.defineProperty(o, "a", {
  get() {
    return 1;
  },
}); // throws a TypeError
// (even though the new get does exactly the same thing)
Object.defineProperty(o, "a", {
  value: 12,
}); // throws a TypeError
// ('value' can be changed when 'configurable' is false, but only when the property is a writable data property)

console.log(o.a); // 1
delete o.a; // Nothing happens; throws an error in strict mode
console.log(o.a); // 1

如果 o.aconfigurable 特性為 true,則不會丟擲任何錯誤,並且該屬性最終會被刪除。

這個例子說明了一個不可配置但可寫的資料屬性。該屬性的 value 仍然可以被更改,並且 writable 仍然可以從 true 切換到 false

js
const o = {};
Object.defineProperty(o, "b", {
  writable: true,
  configurable: false,
});
console.log(o.b); // undefined
Object.defineProperty(o, "b", {
  value: 1,
}); // Even when configurable is false, because the object is writable, we may still replace the value
console.log(o.b); // 1
o.b = 2; // We can change the value with assignment operators as well
console.log(o.b); // 2
// Toggle the property's writability
Object.defineProperty(o, "b", {
  writable: false,
});
Object.defineProperty(o, "b", {
  value: 1,
}); // TypeError: because the property is neither writable nor configurable, it cannot be modified
// At this point, there's no way to further modify 'b'
// or restore its writability

這個例子說明了一個可配置但不可寫的資料屬性。該屬性的 value 仍然可以透過 defineProperty 替換(但不能透過賦值運算子),並且 writable 可以被切換。

js
const o = {};
Object.defineProperty(o, "b", {
  writable: false,
  configurable: true,
});
Object.defineProperty(o, "b", {
  value: 1,
}); // We can replace the value with defineProperty
console.log(o.b); // 1
o.b = 2; // throws TypeError in strict mode: cannot change a non-writable property's value with assignment

這個例子說明了一個不可配置且不可寫的資料屬性。沒有任何方法可以更新該屬性的任何特性,包括它的 value

js
const o = {};
Object.defineProperty(o, "b", {
  writable: false,
  configurable: false,
});
Object.defineProperty(o, "b", {
  value: 1,
}); // TypeError: the property cannot be modified because it is neither writable nor configurable.

新增屬性和預設值

考慮特性預設值的應用方式非常重要。如下例所示,使用屬性訪問器賦值和使用 Object.defineProperty() 之間通常存在差異。

js
const o = {};

o.a = 1;
// is equivalent to:
Object.defineProperty(o, "a", {
  value: 1,
  writable: true,
  configurable: true,
  enumerable: true,
});

// On the other hand,
Object.defineProperty(o, "a", { value: 1 });
// is equivalent to:
Object.defineProperty(o, "a", {
  value: 1,
  writable: false,
  configurable: false,
  enumerable: false,
});

自定義 Setter 和 Getter

下面的例子展示瞭如何實現一個自存檔物件。當 temperature 屬性被設定時,archive 陣列會得到一個日誌條目。

js
function Archiver() {
  let temperature = null;
  const archive = [];

  Object.defineProperty(this, "temperature", {
    get() {
      console.log("get!");
      return temperature;
    },
    set(value) {
      temperature = value;
      archive.push({ val: temperature });
    },
  });

  this.getArchive = () => archive;
}

const arc = new Archiver();
arc.temperature; // 'get!'
arc.temperature = 11;
arc.temperature = 13;
arc.getArchive(); // [{ val: 11 }, { val: 13 }]

在這個例子中,getter 總是返回相同的值。

js
const pattern = {
  get() {
    return "I always return this string, whatever you have assigned";
  },
  set() {
    this.myName = "this is my name string";
  },
};

function TestDefineSetAndGet() {
  Object.defineProperty(this, "myProperty", pattern);
}

const instance = new TestDefineSetAndGet();
instance.myProperty = "test";
console.log(instance.myProperty);
// I always return this string, whatever you have assigned

console.log(instance.myName); // this is my name string

屬性的繼承

如果一個存取器屬性被繼承,當在後代物件上訪問和修改該屬性時,它的 getset 方法將被呼叫。如果這些方法使用一個變數來儲存值,那麼這個值將被所有物件共享。

js
function MyClass() {}

let value;
Object.defineProperty(MyClass.prototype, "x", {
  get() {
    return value;
  },
  set(x) {
    value = x;
  },
});

const a = new MyClass();
const b = new MyClass();
a.x = 1;
console.log(b.x); // 1

這可以透過將值儲存在另一個屬性中來解決。在 getset 方法中,this 指向用於訪問或修改屬性的物件。

js
function MyClass() {}

Object.defineProperty(MyClass.prototype, "x", {
  get() {
    return this.storedX;
  },
  set(x) {
    this.storedX = x;
  },
});

const a = new MyClass();
const b = new MyClass();
a.x = 1;
console.log(b.x); // undefined

與存取器屬性不同,資料屬性總是設定在物件本身上,而不是在原型上。但是,如果繼承了一個不可寫的資料屬性,它仍然會被阻止在物件上被修改。

js
function MyClass() {}

MyClass.prototype.x = 1;
Object.defineProperty(MyClass.prototype, "y", {
  writable: false,
  value: 1,
});

const a = new MyClass();
a.x = 2;
console.log(a.x); // 2
console.log(MyClass.prototype.x); // 1
a.y = 2; // Ignored, throws in strict mode
console.log(a.y); // 1
console.log(MyClass.prototype.y); // 1

規範

規範
ECMAScript® 2026 語言規範
# sec-object.defineproperty

瀏覽器相容性

另見