likes
comments
collection
share

初识Object.defineProperty

作者站长头像
站长
· 阅读数 11

作用:直接在一个对象上新增一个属性或修改已有属性的值,并返回这个对象。

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为要操作的对象
  • prop为要添加或修改的属性
  • options为配置对象,其中value用于给属性赋值,还有enumerable,writable和configurable属性,以及get和set方法。

下面将逐一讲解这些属性和方法。

enumerable

我们给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

writable

我们用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: '女'}

configurable

如果我们尝试用Object.defineProperty修改属性值,发现会报”重复定义属性”的错误:Uncaught TypeError: Cannot redefine property: sex at Function.defineProperty (<anonymous>) ,这不是啪啪打脸吗

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); // {}

writable和configurable的主要区别:

我们用Object.defineProperty定义了一个属性,writable是控制是否能用点运算符修改这个属性的值;而configurable是控制是否能用Object.defineProperty修改这个属性的值。

get方法

除了value可以给属性赋值,也可以调用get方法赋值,该方法的返回值会被作为属性的值。

例如,我们给obj添加sex属性,通过get方法赋值,但是打印obj却没有找到这个属性。

let obj = {}
Object.defineProperty(obj, 'sex', {
  get(){
    return '男'
  }
})
console.log(obj); // {}
console.log(obj.sex); // 男

这是因为这个属性被藏起来了,当打开这个对象,就会发现sex属性,它的值为(…),点开即可看到值。或者也可以通过点运算符查看属性的值。

初识Object.defineProperty

另外,不能同时用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:'女'
})

set方法

当属性值被修改时,会调用该方法,该方法接收一个参数(也就是被赋予的新值)。

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,否则会报错。