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

浏览器兼容性

另见