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