Object.defineProperty()
Object.defineProperty()
静态方法直接在对象上定义一个新属性,或修改对象上的现有属性,并返回该对象。
试试看
语法
Object.defineProperty(obj, prop, descriptor)
参数
obj
-
要定义属性的对象。
prop
-
一个字符串或
Symbol
,指定要定义或修改的属性的键。 descriptor
-
要定义或修改的属性的描述符。
返回值
传递给函数的对象,并添加或修改了指定的属性。
描述
Object.defineProperty()
允许对对象上的属性进行精确的添加或修改。通过 赋值 进行的正常属性添加会创建在属性枚举 (for...in
、Object.keys()
等) 期间显示的属性,其值可以更改,并且可以 删除。此方法允许从其默认值更改这些额外细节。默认情况下,使用 Object.defineProperty()
添加的属性不可写、不可枚举且不可配置。此外,Object.defineProperty()
使用 [[DefineOwnProperty]]
内部方法,而不是 [[Set]]
,因此它不会调用 设置器,即使属性已经存在。
对象中存在的属性描述符主要有两种类型:数据描述符和访问器描述符。数据描述符是一个具有值的属性,该值可能可写,也可能不可写。访问器描述符是一个由 getter-setter 函数对描述的属性。描述符必须是这两种类型之一;它不能同时是两者。
数据描述符和访问器描述符都是对象。它们共享以下可选键(请注意:此处提到的默认值是在使用 Object.defineProperty()
定义属性的情况下):
configurable
-
当将其设置为
false
时,- 此属性的类型不能在数据属性和访问器属性之间更改,并且
- 该属性不能删除,并且
- 其描述符的其他属性不能更改(但是,如果它是一个数据描述符,并且
writable: true
,则value
可以更改,并且writable
可以更改为false
)。
默认为
false
。 enumerable
-
当且仅当此属性在对相应对象上的属性进行枚举时显示时为
true
。默认为false
。
数据描述符还有以下可选键
value
-
与属性关联的值。可以是任何有效的 JavaScript 值(数字、对象、函数等)。默认为
undefined
。 writable
-
true
如果与属性关联的值可以使用 赋值运算符 更改。默认为false
。
访问器描述符还有以下可选键
get
-
用作属性 getter 的函数,或者
undefined
,如果不存在 getter。访问属性时,将调用此函数,不带参数,并且this
设置为通过它访问属性的对象(由于继承,这可能不是定义属性的对象)。返回值将用作属性的值。默认为undefined
。 set
-
用作属性 setter 的函数,或者
undefined
,如果不存在 setter。为属性赋值时,将调用此函数,带有一个参数(要赋给属性的值),并且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. Recycling same object
function withValue(value) {
const d =
withValue.d ||
(withValue.d = {
enumerable: false,
writable: false,
configurable: false,
value,
});
// avoiding duplicate operation for assigning value
if (d.value !== value) d.value = value;
return d;
}
// and
Object.defineProperty(obj, "key", withValue("static"));
// if freeze is available, prevents adding or
// removing the object prototype properties
// (value, get, set, enumerable, writable, configurable)
(Object.freeze || Object)(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
属性属性定义了属性是否被 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
属性控制是否可以从对象中删除属性以及是否可以更改其属性(除了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,
});
自定义设置器和获取器
下面的示例展示了如何实现一个自存档对象。当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 }]
在这个示例中,获取器始终返回相同的值。
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 语言规范 # sec-object.defineproperty |
浏览器兼容性
BCD 表格仅在浏览器中加载