Object.defineProperty()
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
语法
Object.defineProperty(obj, prop, descriptor)
参数
obj-
要定义属性的对象。
prop-
一个字符串或
Symbol,指定了要定义或修改的属性的键。 描述符(descriptor)-
要定义或修改的属性的描述符。
返回值
传入函数的对象,其指定属性已被添加或修改。
描述
Object.defineProperty() 允许精确地添加或修改对象上的属性。通过赋值操作添加的普通属性,会出现在属性枚举(for...in、Object.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。
如果一个描述符不具有 value、writable、get 和 set 中的任何一个键,那么它将被视为一个数据描述符。如果一个描述符既是数据描述符(因为它有 value 或 writable),又是存取器描述符(因为它有 get 或 set),则会抛出异常。
这些特性不一定是描述符自身的属性。继承的属性也会被考虑。为了确保保留这些默认值,你可以预先冻结描述符对象原型链中的现有对象,显式指定所有选项,或者创建一个null 原型对象。
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。当尝试更改不可配置属性的特性时(除了允许的 value 和 writable),会抛出 TypeError 异常,除非是在数据属性上定义与原始值相同的值。
当当前属性是可配置的时,将一个特性定义为 undefined 会有效地删除它。例如,如果 o.k 是一个存取器属性,Object.defineProperty(o, "k", { set: undefined }) 将会移除 setter,使 k 只剩下 getter 并变为只读。如果新描述符中缺少某个特性,则保留旧描述符中该特性的值(它不会被隐式地重新定义为 undefined)。通过提供一个不同“类型”的描述符,可以在数据属性和存取器属性之间切换。例如,如果新描述符是数据描述符(带有 value 或 writable),原始描述符的 get 和 set 特性都将被丢弃。
示例
创建一个属性
当对象中不存在指定的属性时,Object.defineProperty() 会按照描述创建一个新属性。描述符中可以省略字段,这些字段的默认值会被填入。
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 时,该属性被称为“不可写的”。它不能被重新赋值。尝试写入一个不可写的属性不会改变它,并且在严格模式下会导致错误。
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() 中。更多信息,请参阅属性的可枚举性和所有权。
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 特性控制了属性是否可以从对象中删除,以及它的特性(value 和 writable 除外)是否可以被更改。
这个例子说明了一个不可配置的存取器属性。
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.a 的 configurable 特性为 true,则不会抛出任何错误,并且该属性最终会被删除。
这个例子说明了一个不可配置但可写的数据属性。该属性的 value 仍然可以被更改,并且 writable 仍然可以从 true 切换到 false。
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 可以被切换。
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。
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() 之间通常存在差异。
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 数组会得到一个日志条目。
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 总是返回相同的值。
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
属性的继承
如果一个存取器属性被继承,当在后代对象上访问和修改该属性时,它的 get 和 set 方法将被调用。如果这些方法使用一个变量来存储值,那么这个值将被所有对象共享。
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
这可以通过将值存储在另一个属性中来解决。在 get 和 set 方法中,this 指向用于访问或修改属性的对象。
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
与存取器属性不同,数据属性总是设置在对象本身上,而不是在原型上。但是,如果继承了一个不可写的数据属性,它仍然会被阻止在对象上被修改。
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 |
浏览器兼容性
加载中…