Object.defineProperty 语法
⭐ 应当直接在 Object 构造器对象上调用此方法,而不是在任意一个 Object 类型的实例上调用。
Object.defineProperty(obj, prop, descriptor)
参数
该方法接收三个参数
-
obj: 要定义或修改属性的目标对象。
-
prop: 要定义或修改的属性的名称。
-
descriptor: 要定义或修改的属性描述符。
⭐ Object.defineProperty() 方法接收的三个参数,均为必传,任何一个不传的话都会报错,prop 可以传空字符串,但是好像没有什么意义。descriptor 可以传空对象 {},属性的默认值为 undefined。
(() => {
// 定义对象
const myObject = {};
// 第三个参数可以传{},但不可以不传
Object.defineProperty(myObject, 'lastName', {});
console.log('🚀🚀~ myObject:', myObject);
//第二个参数可以传""
Object.defineProperty(myObject, '', { value: 'hzw' });
console.log('🚀🚀~ myObject:', myObject);
})()
⭐ Object.defineProperty() 方法接收的第一个参数可以是一个数组,那么该方法就会给数组添加一个属性。
(() => {
// 定义空数组
const myObject = [];
Object.defineProperty(myObject, 'lastName', {
value: '123'
});
console.log('🚀🚀~ myObject:', myObject);
console.log('🚀🚀~ myObject.lastName:', myObject.lastName);
})()
⭐ Object.defineProperty() 方法只能对已经存在的对象进行操作,如果对象不存在,不会默认创建新对象,而是会报错。
(() => {
Object.defineProperty(myObject, 'firstName', {
value: "han",
});
})()
⭐ Object.defineProperty() 方法会直接在一个对象上定义一个新属性,或者修改一个对象的现有属性。
(() => {
// 定义对象
const myObject = {
firstName: "ywly"
};
console.log('🚀🚀~ init ~ myObject:', myObject);
// 修改属性
Object.defineProperty(myObject, 'firstName', {
value: "han",
});
console.log('🚀🚀~ updataProp ~ myObject:', myObject);
//添加属性
Object.defineProperty(myObject, 'lastName', {
value: "zhiwei",
});
console.log('🚀🚀~ addProp ~ myObject:', myObject);
})()
可以看到,我分别打印了原始对象,修改属性后的对象,添加属性后的对象。如果第二个参数 prop 存在,则是修改属性操作,如果 prop 不存在,则是添加属性操作。
⭐ Object.defineProperty() 方法会修改原对象,存在返回值,返回值是修改后的对象。
(() => {
const myObject = {};
console.log('🚀🚀~ init ~ myObject:', myObject);
const result = Object.defineProperty(myObject, 'firstName', {
value: "han",
});
console.log('🚀🚀~ defineProperty ~ myObject:', myObject);
console.log('🚀🚀~ result:', result);
})()
可以看到我分别打印了原始对象,经过 Object.defineProperty() 处理过的对象,以及Object.defineProperty() 方法的返回值 result 。
属性描述符
前面提到 Object.defineProperty() 方法接收的第三个参数叫做 属性描述符,那它到底是个什么东西呢。
对象里目前存在的属性描述符有两种主要形式:数据描述符 和 存取描述符 , 这两种描述符都是对象。
数据描述符可选键值
value
属性的值,可以是任何有效的 JavaScript 值(数值,对象,函数等),不设置的话默认为 undefined 。
(() => {
// 定义对象
const myObject = {};
Object.defineProperty(myObject, 'firstName', {
value: 'han'
});
Object.defineProperty(myObject, 'lastName', {});
console.log('🚀🚀~ myObject.firstName:', myObject.firstName); // han
console.log('🚀🚀~ myObject.lastName:', myObject.lastName); // undefined
})()
writable(可写性)
writable 是一个布尔值,决定了目标属性的值(value)是否可以被改变。
⭐ 当 writable 设置为 true 时,使用 赋值运算符 或者 Object.defineProperty() 都可以修改属性的值。
(() => {
// 定义对象
const myObject = {};
// 设置 writable 为 true
Object.defineProperty(myObject, 'firstName', {
value: 'han',
writable: true
});
console.log('🚀🚀~ myObject.firstName:', myObject.firstName); // han
myObject.firstName = 'wang'
console.log('🚀🚀~ myObject.firstName:', myObject.firstName); // wang
Object.defineProperty(myObject, 'firstName', {
value: 'zhang',
});
console.log('🚀🚀~ myObject.firstName:', myObject.firstName); // zhang
})()
⭐ 当 writable 设置为 false 时,使用 赋值运算符 修改属性的值,不会报错,但也不会生效。
(() => {
// 定义对象
const myObject = {};
// 设置 writable 为 false
Object.defineProperty(myObject, 'firstName', {
value: 'han',
writable: false
});
console.log('🚀🚀~ myObject.firstName:', myObject.firstName); // han
myObject.firstName = 'wang'
console.log('🚀🚀~ myObject.firstName:', myObject.firstName); // han
})()
⭐ 当 writable 设置为 false 时,使用 Object.defineProperty() 修改属性的值,会直接报错。
(() => {
// 定义对象
const myObject = {};
// 设置 writable 为 false
Object.defineProperty(myObject, 'firstName', {
value: 'han',
writable: false
});
Object.defineProperty(myObject, 'firstName', {
value: 'wang',
});
console.log('🚀🚀~ myObject.firstName:', myObject.firstName);
})()
存取描述符可选键值
get
属性的 getter 函数,当访问该属性时,会调用此函数。执行时不传入任何参数,但是会传入 this 对象(由于继承关系,这里的this并不一定是定义该属性的对象)。该函数的返回值会被用作属性的值。如果没有定义 getter,则为 undefined。
set
属性的 setter 函数,当属性值被修改时,会调用此函数。该方法接受一个参数(也就是被赋予的新值),会传入赋值时的 this 对象。 如果没有定义 setter,则为 undefined。
(() => {
// 定义对象
const myObject = {};
Object.defineProperty(myObject, 'firstName', {
get() {
console.log('🚀🚀~ 读取了myObject.firstName:');
return this.value;
},
set(newValue) {
console.log('🚀🚀~ 修改了myObject.firstName:');
this.value = newValue;
},
});
myObject.firstName = 'wang'
console.log('🚀🚀~ myObject.firstName:', myObject.firstName);
})()
共享键值
以下两个是 存取描述符 和 数据描述符 允许共同拥有的键值。
enumerable (可遍历性)
enumerable 是一个布尔值,表示目标属性在 for..in、Object.keys、JSON.stringify 中是否可遍历。
⭐ 当 enumerable 设置为 true 时,目标属性可被遍历操作。
(() => {
// 定义对象
const myObject = {
age: "23",
sex: "boy"
};
// 设置 enumerable 为 true
Object.defineProperty(myObject, 'firstName', {
value: 'han',
enumerable: true,
});
for (const key in myObject) {
console.log('🚀🚀~ for in', key);
}
console.log('🚀🚀~ Object.keys:', Object.keys(myObject));
console.log('🚀🚀~ JSON.stringify:', JSON.stringify(myObject));
})()
⭐ 当 enumerable 设置为 false 时,目标属性不可被遍历操作。
(() => {
// 定义对象
const myObject = {
age: "23",
sex: "boy"
};
// 设置 enumerable 为 false
Object.defineProperty(myObject, 'firstName', {
value: 'han',
enumerable: false,
});
for (const key in myObject) {
console.log('🚀🚀~ for in', key);
}
console.log('🚀🚀~ Object.keys:', Object.keys(myObject));
console.log('🚀🚀~ JSON.stringify:', JSON.stringify(myObject));
})()
⭐ for...in 循环包括继承的属性,Object.keys 方法不包括继承的属性。如果需要获取对象自身的所有属性,不管是否可遍历,可以使用 Object.getOwnPropertyNames 方法。
configurable (可配置性)
configurable 是一个布尔值,决定了是否可以修改 属性描述对象 以及表示能否通过 delete 删除属性。
⭐ 当 configurable 为 false 时,不能否通过 delete 删除属性。
(() => {
// 定义对象
const myObject = {};
// 设置 configurable 为 false
Object.defineProperty(myObject, 'firstName', {
value: 'han',
configurable: false,
});
// 设置 configurable 为 true
Object.defineProperty(myObject, 'lastName', {
value: 'zhiwei',
configurable: true,
});
console.log('🚀🚀 before delete ~ myObject.firstName :', myObject.firstName);
console.log('🚀🚀 before delete ~ myObject.lastName :', myObject.lastName);
delete myObject.firstName
delete myObject.lastName
console.log('🚀🚀 after delete ~ myObject.firstName :', myObject.firstName);
console.log('🚀🚀 after delete ~ myObject.lastName :', myObject.lastName);
})()
可以看到 configurable 为 false 时,delete 属性不会报错,但也不会生效。
可以看到 configurable 为 true 时,delete 属性会生效,属性变为 undefined。
⭐ 当 configurable 为 true 并且 writable 为 true 时,通过 赋值运算符 修改 value 的修改才会生效。
(() => {
// 定义对象
const myObject = {};
// 设置 configurable 为 true,writable 默认为 false
Object.defineProperty(myObject, 'firstName', {
value: 'han',
configurable: true,
});
// 设置 configurable 为 true,writable 为 true
Object.defineProperty(myObject, 'lastName', {
value: 'zhiwei',
configurable: true,
writable: true,
});
console.log('🚀🚀 before change ~ myObject.firstName :', myObject.firstName); //han
myObject.firstName = 'zhang'
console.log('🚀🚀 after change ~ myObject.firstName :', myObject.firstName); //han 没有生效
console.log('🚀🚀 before change ~ myObject.lastName :', myObject.lastName); //zhiwei
myObject.lastName = 'san'
console.log('🚀🚀 after change ~ myObject.lastName :', myObject.lastName); //san 生效了
})()
⭐ 当 configurable 和 writable 其中有任意一个为 true 时,通过 Object.defineProperty()方法修改属性的值,就会生效。
(() => {
// 定义对象
const myObject = {};
// 设置 configurable 为 true,writable 为 false
Object.defineProperty(myObject, 'firstName', {
value: 'han',
configurable: true,
writable: false
});
Object.defineProperty(myObject, 'firstName', {
value: 'wang',
});
console.log('🚀🚀~ myObject.firstName:', myObject.firstName); // wang
})()
(() => {
// 定义对象
const myObject = {};
// 设置 configurable 为 false,writable 为 true
Object.defineProperty(myObject, 'firstName', {
value: 'han',
configurable: false,
writable: true
});
Object.defineProperty(myObject, 'firstName', {
value: 'wang',
});
console.log('🚀🚀~ myObject.firstName:', myObject.firstName); // wang
})()
⭐ 当 configurable 为 false 时, enumerable, configurable 都不可修改,会直接报错。
(() => {
// 定义对象
const myObject = {};
// 设置 enumerable 为 true
Object.defineProperty(myObject, 'firstName', {
value: 'han',
configurable: false,
enumerable: true,
});
// 设置 enumerable 为 false
Object.defineProperty(myObject, 'firstName', {
value: 'wang',
configurable: false,
enumerable: false,
});
console.log('🚀🚀~ Descriptor:', Object.getOwnPropertyDescriptor(myObject, 'firstName'));
})()
⭐ 当 configurable 为 false 时, writable 可以由 true 改为 false ,但不可由 false 改为 true ,否则会报错。
(() => {
// 定义对象
const myObject = {};
// 设置 writable 为 true
Object.defineProperty(myObject, 'firstName', {
value: 'han',
configurable: false,
writable: true
});
// 设置 writable 为 false
Object.defineProperty(myObject, 'firstName', {
value: 'wang',
configurable: false,
writable: false,
});
console.log('🚀🚀~ Descriptor:', Object.getOwnPropertyDescriptor(myObject, 'firstName'));
})()
⭐ 当 configurable 为 false 时, getter 和 setter 方法无法被修改。
(() => {
// 定义对象
const myObject = {};
Object.defineProperty(myObject, 'firstName', {
configurable: false,
get() {
console.log('🚀🚀~ 读取了myObject.firstName:');
return this.value;
},
set(newValue) {
console.log('🚀🚀~ 修改了myObject.firstName:');
this.value = newValue;
},
});
Object.defineProperty(myObject, 'firstName', {
configurable: false,
get() {
console.log('🚀🚀~ 读取了myObject.firstName:');
return this.value + 'y';
},
set(newValue) {
console.log('🚀🚀~ 修改了myObject.firstName:');
this.value = newValue + 'y';
},
});
})()
描述符默认值
通过字面量方式定义属性
(() => {
//configurable/writable/enumerable 默认都是 true
const myObject = {
firstName: "han"
};
console.log('🚀🚀~ Descriptor:', Object.getOwnPropertyDescriptor(myObject, 'firstName'));
})()
通过构造函数方式定义属性
(() => {
//configurable/writable/enumerable 默认都是 true
function Obj(firstName) {
this.firstName = firstName;
}
let myObject = new Obj("han");
console.log('🚀🚀~ Descriptor:', Object.getOwnPropertyDescriptor(myObject, 'firstName'));
})()
通过 new Object() 定义属性
(() => {
const myObject = new Object();
myObject.firstName = "han";
console.log('🚀🚀~ Descriptor:', Object.getOwnPropertyDescriptor(myObject, 'firstName'));
})()
通过 Object.defineProperty () 定义属性
(() => {
const myObject = {};
//configurable/writable/enumerable 默认都是 false
Object.defineProperty(myObject, 'firstName', {
value: 'han',
});
console.log('🚀🚀~ Descriptor:', Object.getOwnPropertyDescriptor(myObject, 'firstName'));
})()
属性描述符小结
描述符可拥有的键值
| configurable | enumerable | value | writable | get | set | |
|---|---|---|---|---|---|---|
| 数据描述符 | √ | √ | √ | √ | X | X |
| 存取描述符 | √ | √ | X | X | √ | √ |
⭐ 一个描述符只能是这两者其中之一,不能同时是两者。
⭐ 如果一个描述符不具有 value、writable、get 和 set 中的任意一个键,那么它将被认为是一个数据描述符。
⭐ 如果一个描述符同时拥有 value 或 writable 和 get 或 set 键,则会产生一个异常。
实现一个简单的双向绑定
//html
<body>
<h1>Object.defineProperty</h1>
<input type="text" id="input" />
<div id="div"></div>
</body>
//js
(() => {
const obj = {};
const inputVal = document.getElementById("input");
const div = document.getElementById("div");
Object.defineProperty(obj, "name", {
set: function(newVal) {
inputVal.value = newVal;
div.innerHTML = newVal;
}
});
inputVal.addEventListener('input', (e) => {
obj.name = e.target.value;
});
})()
其它相关的方法
Object.defineProperties()
定义多个属性,内部循环调用 Object.defineProperty。
Object.prototype.propertyIsEnumerable()
propertyIsEnumerable() 方法返回一个布尔值,表示指定的属性是否可枚举。
Object.entries()
Object.entries() 方法返回一个给定对象自身可枚举属性的键值对数组,其排列与使用 for...in 循环遍历该对象时返回的顺序一致(区别在于 for-in 循环还会枚举原型链中的属性)。
Object.values()
Object.values() 方法返回一个给定对象自身的所有可枚举属性值的数组,值的顺序与使用for...in循环的顺序相同 ( 区别在于 for-in 循环枚举原型链中的属性 )。
Object.keys()
Object.keys() 方法会返回一个由一个给定对象的自身可枚举属性组成的数组,数组中属性名的排列顺序和正常循环遍历该对象时返回的顺序一致。
Object.freeze()
Object.freeze() 方法可以冻结一个对象。一个被冻结的对象再也不能被修改;冻结了一个对象则不能向这个对象添加新的属性,不能删除已有属性,不能修改该对象已有属性的可枚举性、可配置性、可写性,以及不能修改已有属性的值。
Object.isFrozen()
Object.isFrozen() 方法判断一个对象是否被冻结。
Object.getOwnPropertyDescriptors()
Object.getOwnPropertyDescriptors() 方法用来获取一个对象的所有自身属性的描述符。
Object.preventExtensions()
Object.preventExtensions() 方法让一个对象变的不可扩展,也就是永远不能再添加新的属性。
Object.isExtensible()
Object.isExtensible() 方法判断一个对象是否是可扩展的(是否可以在它上面添加新的属性)。
Object.isSealed()
Object.isSealed() 方法判断一个对象是否被密封。
Object.seal()
Object.seal()方法封闭一个对象,阻止添加新属性并将所有现有属性标记为不可配置。当前属性的值只要原来是可写的就可以改变。
参考
兼容性👉👉 Can I use