let obj = {
age: 18
}
// 给obj新增一个属性name
Object.defineProperty(obj, 'name', {
value: 'liHua'
})
// 修改obj的age属性的值
const p = Object.defineProperty(obj, 'age', {
value: 99,
})
console.log(obj.name); // liHua
console.log(obj.age); // 99
console.log(p === obj, p); // true {age: 99, name: 'liHua'}
可见,该方法的语法为:Object.defineProperty(obj, prop, options)
下面将逐一讲解这些属性和方法。
我们给obj用Object.defineProperty添加sex属性,用for…in…循环遍历,结果没有找到这个属性。但是我们却能通过点运算符获取属性值,这是为什么?
let obj = {}
obj.age = 18
Object.defineProperty(obj, 'sex', {
value: '男'
})
for (let item in obj) {
console.log('for循环遍历的元素',item);
}
// for循环遍历的元素 age
console.log(obj.sex); // 男
原来,如果我们通过点运算符直接给对象添加属性,options中的属性都默认为true。
例如,上面的obj.age = 18
等同于下面的代码,其中,enumerable为true,则表示该属性被可枚举。
Object.defineProperty(obj, "age", {
value: 18,
writable: true,
configurable: true,
enumerable: true
});
而通过Object.defineProperty添加的属性,options中的属性都默认为false。
例如:Object.defineProperty(obj, 'sex', { value: '男' })
等同于下面的代码,enumerable为false,自然就不能通过遍历的方式找到它了。
Object.defineProperty(obj, "sex", {
value: '男',
writable: false,
configurable: false,
enumerable: false
});
如果我们想要遍历这个元素,只需要将属性设置为可被枚举即可。
let obj = {}
obj.age = 18
Object.defineProperty(obj, 'sex', {
value: '男',
enumerable:true
})
for (let item in obj) {
console.log('for循环遍历的元素', item);
}
// for循环遍历的元素 age
// for循环遍历的元素 sex
我们用Object.defineProperty给obj添加sex属性,然后修改age和sex属性,发现age属性修改成功,而sex属性并没有修改成功。
let obj = {age:18}
Object.defineProperty(obj, 'sex', {
value: '男',
})
obj.age = 20
obj.sex = '女'
console.log(obj);
// {age: 20, sex: '男'}
这也是因为用该方法定义的属性的writable默认为false,表示不可写的,不能被赋值运算符改变。如果想要修改属性的值,可以将writable改为true,或者通过Object.defineProperty修改属性值。
let obj = {
age: 18
}
Object.defineProperty(obj, 'sex', {
value: '男',
writable:true
})
obj.age = 20
obj.sex = '女'
console.log(obj);
// {age: 20, sex: '女'}
如果我们尝试用Object.defineProperty修改属性值,发现会报”重复定义属性”的错误:Uncaught TypeError: Cannot redefine property: sex at Function.defineProperty (
,这不是啪啪打脸吗
let obj = {}
Object.defineProperty(obj, 'sex', {
value: '男',
})
Object.defineProperty(obj, 'sex', {
value: '女',
})
其实,这是因为configurable默认为false,则不能通过Object.defineProperty改变某属性的值,所以会报错。只需要一开始将configurable设置为true,即可修改成功。
let obj = {}
Object.defineProperty(obj, 'sex', {
value: '男',
configurable:true
})
Object.defineProperty(obj, 'sex', {
value: '女',
})
console.log(obj.sex); // 女
同时,configurable表示对象的属性是否可以被删除。还能控制enumerable,configurable,set和get方法是否能被修改。
如下例,删除sex属性失败,因为用Object.defineProperty定义的属性,configurable默认为false
let obj = {}
Object.defineProperty(obj, 'sex', {
value:'男',
})
delete obj.sex
console.log(obj); // {sex: '男'}
configurable改为true后,即可删除成功。
let obj = {}
Object.defineProperty(obj, 'sex', {
value:'男',
configurable:true
})
delete obj.sex
console.log(obj); // {}
我们用Object.defineProperty定义了一个属性,writable是控制是否能用点运算符修改这个属性的值;而configurable是控制是否能用Object.defineProperty修改这个属性的值。
除了value可以给属性赋值,也可以调用get方法赋值,该方法的返回值会被作为属性的值。
例如,我们给obj添加sex属性,通过get方法赋值,但是打印obj却没有找到这个属性。
let obj = {}
Object.defineProperty(obj, 'sex', {
get(){
return '男'
}
})
console.log(obj); // {}
console.log(obj.sex); // 男
这是因为这个属性被藏起来了,当打开这个对象,就会发现sex属性,它的值为(…),点开即可看到值。或者也可以通过点运算符查看属性的值。
另外,不能同时用get方法和value属性赋值。
如下面的代码会报错:Uncaught TypeError: Invalid property descriptor. Cannot both specify accessors and a value or writable attribute
let obj = {}
Object.defineProperty(obj, 'sex', {
get(){
return '男'
},
value:'女'
})
当属性值被修改时,会调用该方法,该方法接收一个参数(也就是被赋予的新值)。
let obj = {}
Object.defineProperty(obj, 'sex', {
get(){
return '男'
},
set(newValue){
console.log(`有人要修改sex的值,值是${newValue}`);
},
})
obj.sex = '女'
// 控制台输出: 有人修改了sex的值,新值是女
因为get和set都是方法,可以执行别的操作,例如:当值改变了,就去更新页面,vue2的响应式就是通过Object.defineProperty的get和set实现的。
下面的例子,将p和person联系起来了,person就像是程序员定义的属性,经过处理后,p中含有person中的所有属性,我们再将p给程序员使用。如果要获取name属性,就返回person.name的值;如果要修改name属性,set方法也能监听到,就重新给person.name赋值,同时更新页面的值,从而实现响应式更新。
const person = {name: 'lisi'}
let p = {}
// 给p添加name属性
Object.defineProperty(p, 'name', {
get() {
console.log('有人获取p中的name属性了');
return person.name
},
set(newValue) {
console.log(`有人修改p中的name属性了,值为${newValue},我要更新页面`);
person.name = newValue
}
})
值得注意的是:定义了get或set,就不能定义writable或value,否则会报错。