我们对一个Object对象设置属性时,一般是通过对象的.
操作符或者[]
操作符直接赋值的,例如obj1.a = 1 或 obj1['a'] = 1
,通过这种方式添加的属性后续可以更改属性值,并且默认该属性是可枚举的,即通过for (const key in obj1) 或 obj1.keys()
均可访问到属性。如果我们想在新增属性后不允许再更改属性值或者将该属性设置为非枚举属性,那我们该如何处理呢?
此时我们就需要使用静态方法Object.defineProperty(obj, prop, descriptor)
,其可以通过定义属性的元数据信息精确地控制属性的行为。
Object.defineProperty(obj, prop, descriptor)
obj
prop
descriptor
该方法允许精确添加或修改对象的属性。通过赋值操作添加的普通属性是可枚举的,能够在属性枚举期间呈现出来(for...in 或 Object.keys 方法)
, 这些属性的值可以被改变,也可以被删除。方法Object.defineProperty()
允许修改默认的属性元数据配置。我们可以认为使用Object.defineProperty()
定义的属性在使用上更加严格。
Object.defineProperty(obj, prop, descriptor)
中的参数descriptor
就是属性描述符,就是定义属性行为的元数据信息。属性描述符有两种主要形式:数据描述符和存取描述符。数据描述符是一个Boolean类型的元数据属性,值为true或false,用于定义对属性的某种操作行为是允许还是禁止。存取描述符是由getter-setter函数对描述的属性。描述符必须是这两种形式之一,即descriptor
要么是数据描述符,要么是存取描述符,不能同时包含数据描述符和存取描述符。
数据描述符和存取描述符均具有以下可选键值:
数据描述符除了具有configurable
和enumerable
这两个元数据键值外,还具有以下可选键值:
存取描述符除了具有configurable
和enumerable
这两个元数据键值外,还具有以下可选键值:
描述符可同时具有的键值
configurable | enumerable | value | writable | get | set | |
---|---|---|---|---|---|---|
数据描述符 | Yes | Yes | Yes | Yes | No | No |
存取描述符 | Yes | Yes | No | No | Yes | Yes |
如果一个描述符不具有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 '#
// 非严格模式不抛出异常,但是不生效
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 #
// 非严格模式下不会抛出异常,但是不生效
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.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'
Object.defineProperty() | Object.defineProperties() |
---|---|
主流现代浏览器以及IE9+支持,IE8虽然实现了该方法,但只能在 DOM 对象上使用且存在诸多限制。该方法在规范ECMAScript Latest Draft (ECMA-262)中被定义。 | 主流现代浏览器以及IE9+支持,在规范ECMAScript Latest Draft (ECMA-262)中被定义。 |
MDN Object.defineProperty()
MDN Object.defineProperties()
StackOverflow Object.defineProperty polyfill
StackOverflow JavaScript getter support in IE8