[学习文档地址]
目录
为什么要使用Object.defineProperty()?
为什么要使用Object.defineProperty()?
该方法会直接在一个对象上定义一个新属性,或修改其现有属性,并返回此对象。
简单地说,我们对一个Object对象设置属性时,一般是通过对象的.操作符或者[]操作符直接赋值的,例如obj1.a = 1 或 obj1['a'] = 1,通过这种方式添加的属性后续可以更改属性值,并且默认该属性是可枚举的,即通过for (const key in obj1) 或 obj1.keys()均可访问到属性。如果我们想在新增属性后不允许再更改属性值或者将该属性设置为非枚举属性,那我们该如何处理呢?
此时我们就需要使用静态方法Object.defineProperty(obj, prop, descriptor),其可以通过定义属性的元数据信息精确地控制属性的行为。
Object.defineProperty()工作原理
1.首先,检查传入的参数是否为对象。如果不是对象,则抛出 TypeError
。
2.接着,创建或修改指定对象的属性,需要传入三个参数:目标对象 obj
、属性名 prop
和属性描述符 descriptor
。
3.属性描述符 descriptor
是一个包含属性特性的对象,可以包括 value
、writable
、enumerable
、configurable
、get
和 set
等属性。
4.根据属性描述符中的设置,对属性进行定义或修改。例如,可以设置属性的值、可写性、可枚举性和可配置性等。
5.创建或修改属性后,返回目标对象
语法
Object.defineProperty(obj, prop, descriptor)
参数
obj
要定义属性的对象。
prop
一个字符串或 Symbol,指定了要定义或修改的属性键。
descriptor
要定义或修改的属性的描述符。
返回值
传入函数的对象,其指定的属性已被添加或修改
方法描述
该方法允许精确添加或修改对象的属性。通过赋值操作添加的普通属性是可枚举的,能够在属性枚举期间呈现出来(例如 for...in、Object.keys() 等), 这些属性的值可以被改变,也可以被删除。方法Object.defineProperty()允许修改默认的属性元数据配置。我们可以认为使用Object.defineProperty()定义的属性在使用上更加严格。
属性描述符
Object.defineProperty(obj, prop, descriptor)中的参数descriptor就是属性描述符,就是定义属性行为的元数据信息。属性描述符有两种主要形式:数据描述符和存取描述符。数据描述符是一个Boolean类型的元数据属性,值为true或false,用于定义对属性的某种操作行为是允许还是禁止。存取描述符是由getter-setter函数对描述的属性。描述符必须是这两种形式之一,即descriptor要么是数据描述符,要么是存取描述符,不能同时包含数据描述符和存取描述符。
数据描述符和访问器描述符都是对象。
它们共享以下可选键
(请注意:在使用 Object.defineProperty()
定义属性的情况下,下述所有键都是默认值):
当设置为 false
时,
- 该属性的类型不能在数据属性和访问器属性之间更改,且
- 该属性不可被删除,且
- 其描述符的其他属性也不能被更改(但是,如果它是一个可写的数据描述符,则
value
可以被更改,writable
可以更改为false
)。
默认值为 false
。
数据描述符还具有以下可选键值:
value
与属性相关联的值。可以是任何有效的 JavaScript 值(数字、对象、函数等)。默认值为 undefined。
writable
如果与属性相关联的值可以使用赋值运算符更改,则为 true
。默认值为 false
。
访问器描述符还具有以下可选键值:
get
用作属性 getter 的函数,如果没有 getter 则为 undefined 。当访问该属性时,将不带参地调用此函数,并将 this
设置为通过该属性访问的对象(因为可能存在继承关系,这可能不是定义该属性的对象)。返回值将被用作该属性的值。默认值为 undefined 。
set
用作属性 setter 的函数,如果没有 setter 则为 undefined 。当该属性被赋值时,将调用此函数,并带有一个参数(要赋给该属性的值),并将 this
设置为通过该属性分配的对象。默认值为 undefined 。
如果描述符没有 value
、writable
、get
和 set
键中的任何一个,它将被视为数据描述符。如果描述符同时具有 [value
或 writable
] 和 [get
或 set
] 键,则会抛出异常。
示例
- 创建属性
如果对象中不存在指定的属性,Object.defineProperty()就创建这个属性。当描述符中省略某些字段时,这些字段将使用它们的默认值。拥有布尔值的字段的默认值都是false。value,get和set字段的默认值为undefined。一个没有get/set/value/writable定义的属性被称为“通用的”,并被“键入”为一个数据描述符。
向对象中添加一个属性并设置数据描述符
const obj1 = {};
// 向对象obj1中添加一个属性a,并设置数据描述符的示例
Object.defineProperty(obj1, "a", {
value: 1,
writable: true,
enumerable: true,
configurable: true
});
// 对象obj1有了属性a,其值为1
console.log(obj1.a) // 输出: 1,表示属性a的【value: 1】默认值已生效
向对象中添加一个属性并设置存取描述符
const obj1 = {};
// 向对象中添加一个属性b,并设置存取描述符的示例
var bValue;
Object.defineProperty(obj1, "b", {
get: function () {
return bValue;
},
set: function (newValue) {
bValue = newValue;
},
enumerable: true,
configurable: true
});
obj1.b = 2;
// 对象obj1有了属性b,其值为2
console.log(obj1.b); // 输出: 2,表示属性b的getter和setter均生效
数据描述符和存取描述符不能混合使用
const obj1 = {};
// 数据描述符和存取描述符不能混合使用
// 抛出异常: Invalid property descriptor. Cannot both specify accessors and a value or writable attribute
Object.defineProperty(obj1, "c", {
value: 1,
get: function () {
return 2;
}
});
- writable
当writable
设置为false时(默认值即是false),该属性被定义为只读属性,即只能读取该属性值,不能给该属性写入值。
const obj1 = {};
Object.defineProperty(obj1, 'a', {
value: 1, // 用于定义a的默认值为1
writable: true // 用于定义属性a可写
});
Object.defineProperty(obj1, 'b', {
value: 2, // 用于定义b的默认值为2
writable: false // 用于定义属性b不可写
});
obj1.a = 100; // 向属性a中写入值
console.log(obj1.a); // 输出: 100,表示属性a的【writable: true】已生效
// 严格模式下抛出异常 TypeError: Cannot assign to read only property 'b' of object '#<Object>'
// 非严格模式不抛出异常,但是不生效
obj1.b = 200; // 向属性b中写入值
console.log(obj1.b); // 输出: 1
- enumerable
enumerable
定义了对象的属性是否可以在 for...in
循环和 Object.keys()
中被枚举。
const obj1 = {};
Object.defineProperty(obj1, 'a', {
value: 1, // 用于定义a的默认值为1
enumerable: true // 用于定义属性a可以被枚举
});
Object.defineProperty(obj1, 'b', {
value: 2, // 用于定义b的默认值为2
enumerable: false // 用于定义属性b不可以被枚举
});
for (let key in obj1) {
console.log(key); // 输出: 'a'
}
console.log(Object.keys(obj1)); // 输出: ['a']
console.log('a' in obj1); // 输出: true
console.log('b' in obj1); // 输出: true
- get和set
设置getter和setter的普通示例
const obj1 = {};
let v = null;
Object.defineProperty(obj1, 'a', {
get: function() {
return v;
},
set: function(newValue) {
v = newValue;
}
});
obj1.a = 1;
console.log(obj1.a); // 输出: 1,表示属性a的getter和setter均生效
继承属性
function Person() {}
// 在Person的原型上定义属性name,并设置getter以及setter,这样Person类的示例均能访问name属性,且实例的name属性互相隔离
Object.defineProperty(Person.prototype, 'name', {
get: function() {
return this._name;
},
set: function(newValue) {
this._name = newValue;
}
});
const a = new Person();
const b = new Person();
a.name = 'zhangsan';
b.name = 'lisi';
console.log(a.name); // 输出: 'zhangsan'
console.log(b.name); // 输入: 'lisi'
- configurable
configurable
特性表示对象的属性是否可以被删除,以及能否通过再次调用Object.defineProperty()更改其他属性描述符的配置。
configurable
用于表示对象的属性是否可以被删除的示例
// 'use strict';
const obj1 = {};
Object.defineProperty(obj1, 'a', {
value: 1, // 用于定义a的默认值为1
configurable: true
});
Object.defineProperty(obj1, 'b', {
value: 2, // 用于定义b的默认值为2
configurable: false
});
delete obj1.a; // 从对象obj1中删除属性a
console.log(obj1.a); // 输出: undefined,表示删除属性a成功
// 严格模式下抛出异常 TypeError: Cannot delete property 'b' of #<Object>
// 非严格模式下不会抛出异常,但是不生效
delete obj1.b; // 从对象obj2中删除属性b
console.log(obj1.b); // 输出: 2,表示删除属性b失败
configurable
值为false表示属性描述符不能被再次修改的示例
// 'use strict';
const obj1 = {};
Object.defineProperty(obj1, 'a', {
value: 1, // 用于定义a的默认值为1
configurable: false,
writable: false, // 将属性a定义为只读不可写
enumerable: false // 将属性a定义为不可枚举
});
// 严格模式会抛出异常
// 非严格模式不会抛出异常,但不生效
obj1.a = 100;
console.log(obj1.a); // 输出: 1
console.log(Object.keys(obj1)); // 输出: []
// 抛出异常TypeError: Cannot redefine property: a
// 说明当上次对属性a调用Object.defineProperty()设置configurable为false后,a的属性描述符不能被再次修改,即不能再次对属性a调用Object.defineProperty()方法
Object.defineProperty(obj1, 'a', {
value: 2, // 用于定义a的默认值为1
configurable: true,
writable: true, // 重新将属性a定义为可写
enumerable: true // 重新将属性a定义为可枚举
});
// 由于上面抛出异常,以下代码均无法执行
// console.log(obj1.a);
// obj1.a = 200;
// console.log(obj1.a);
// console.log(Object.keys(obj1));
configurable
值为true表示属性描述符可以被再次修改的示例
// 'use strict';
const obj1 = {};
Object.defineProperty(obj1, 'a', {
value: 1, // 用于定义a的默认值为1
configurable: true, // 表示可以对属性a再次调用Object.defineProperty()方法
writable: false, // 将属性a定义为只读不可写
enumerable: false // 将属性a定义为不可枚举
});
obj1.a = 100;
console.log(obj1.a); // 输出: 1
console.log(Object.keys(obj1)); // 输出: []
Object.defineProperty(obj1, 'a', {
value: 2, // 用于定义a的默认值为2
configurable: true,
writable: true, // 重新将属性a定义为可写
enumerable: true // 重新将属性a定义为可枚举
});
console.log(obj1.a); // 输出: 2,表示【writable: true】生效
obj1.a = 200;
console.log(obj1.a); // 输出: 200,表示【writable: true】生效
console.log(Object.keys(obj1)); // 输出: ['a'],表示【enumerable: true】生效
let bValue = null;
Object.defineProperty(obj1, 'b', {
configurable: true, // 表示可以对属性a再次调用Object.defineProperty()方法
get() {
return bValue;
},
set(newValue) {
bValue = newValue;
}
});
obj1.b = 'abcd';
console.log(obj1.b); // 输出: 'abcd'
Object.defineProperty(obj1, 'b', {
configurable: true, // 表示可以对属性a再次调用Object.defineProperty()方法
get() {
return bValue.toString().toUpperCase();
},
set(newValue) {
bValue = newValue + '_' + newValue;
}
});
obj1.b = 'efgh';
console.log(obj1.b); // 输出: 'EFGH_EFGH',表示新的setter和getter已经生效
其他方法
Object.defineProperties()
- 描述静态方法Object.defineProperty(obj, prop, descriptor)只能每次对一个属性设置元数据配置信息,即每次只能对一个属性设置getter和setter,为了能够对多个属性同时设置元数据配置信息,Object还提供了静态方法Object.defineProperties(obj, props)。参数props是包含多个key-value的键值对对象,其中key是就是静态方法Object.defineProperty(obj, prop, descriptor)中的 prop 参数,value就是静态方法Object.defineProperty(obj, prop, descriptor)中的 descriptor 参数。
-
const obj1 = { firstName: '', lastName: '', _job: '' }; Object.defineProperties(obj1, { fullName: { get: function () { return this.firstName + ' ' + this.lastName; }, set: function(v) { const names = v.split(' '); this.firstName = names[0]; this.lastName = names[1]; } }, job: { get: function() { return this._job.toString().toUpperCase(); }, set: function(v) { this._job = v; } } }); obj1.fullName = 'Jackie Chan'; console.log(obj1.fullName); // 输出: 'Jackie Chan' console.log(obj1.firstName); // 输出: 'Jackie' console.log(obj1.lastName); // 输出: 'Chan' obj1.job = 'web developer'; console.log(obj1.job); // 输出: 'WEB DEVELOPER'
浏览器兼容性
Report problems with this compatibility data on GitHubdesktop mobile server Chrome
Edge
Firefox
Opera
Safari
Chrome Android
Firefox for Android
Opera Android
Safari on iOS
Samsung Internet
WebView Android
Deno
Node.js
defineProperty
5
Toggle history12
Toggle history4
Toggle history11.6
Toggle history5.1
footnote
Toggle history18
Toggle history4
Toggle history12
Toggle history6
footnote
Toggle history1.0
Toggle history4.4
Toggle history1.0
Toggle history0.10.0
Toggle history