Object.defineProperty 详解

132 阅读4分钟

最近也在那翻面试题,见得最多的,就是Object.defineProperty的解释,他是做啥子的,好吧,我也不太清楚。我们学习下吧。大佬文章在这

Object.defineProperty他是干啥的呢,简单来说就是直接在一个对象上定义一个新属性,或者修改一个已经存在的属性,并返回这个对象。

一般一个对象上定义一个新属性或者修改一个已经存在的属性我们会这么做

let person = {};
person.name="ms";
person.sayHello=()=>{
  console.log('hello')
}
console.log(person);//{name: 'ms', sayHello: ƒ}

那么Object.defineProperty 怎么用?

Object.defineProperty 需要三个参数(object , propName , descriptor)

  • object: 对象 => 给谁加
  • propName: 属性名 => 要加的属性的名字 【类型:String】
  • descriptor: 属性描述 => 加的这个属性有什么样的特性【类型:Object】

这样描述下来,我们可以把上述代码写成

//person.name="ms"; 

Object.defineProperty(person,"name",{
 value:"ms"
})

没错 descriptor 属性中有个 value 特性,代表他的值。我们也可以添加 function,bool,number等等

Object.defineProperty(person,"sayHello",{
 value:()=>{
    console.log('hello')
  }
})

Object.defineProperty(person,"age",{
 value:12
})

Object.defineProperty(person,"isStrong",{
 value:true
})

Object.defineProperty(person,"record",{
 value:{
     math:90,
     music:100,
 }
})

那么我们试下通过Object.defineProperty改变这个值

let person = {};
Object.defineProperty(person,"name",{
 value:"ms"
})
console.log('修改前',person);
Object.defineProperty(person,"name",{
 value:"ms123"
})
console.log('修改后',person);

但是报错了

Cannot redefine property: name

也就是说,只是初始定义一次。那我换种方式呢

let person = {};
person.name = 'ms'
console.log('修改前',person);//ms
Object.defineProperty(person,"name",{
 value:"ms123"
})
console.log('修改后',person);//ms123

修改成功了,那我继续换种方式

let person = {};
Object.defineProperty(person,"name",{
 value:"ms"
})
console.log('修改前',person);
person.name = 'ms123'
console.log('修改后',person);

报错了

Cannot assign to read only property 'name'

他说不能修改只读的属性,哦~,由Object.defineProperty定义的,不做任何特性修改,是处于只读的,那接下来,该咋解决呢

descriptor有很多属性,除了value属性还有个 writable【顾名思义属性是否可以被重新赋值】接受数据类型为 boolean(默认为false) true => 支持被重新赋值 false=>只读

let person = {};
Object.defineProperty(person,"name",{
 value:"ms",
 writable:true,
})
console.log('修改前',person);//ms
person.name = 'ms123'
console.log('修改后',person);//ms123

成功啦!

descriptor 应该还有其他的属性吧,我们来列举出来

enumerable【顾名思义属性是否可以被枚举】接受数据类型为 boolean(默认为false) true => 支持被枚举 false=>不支持

这枚举吗?怎么用啊?

我们想知道一个对象有啥属性,咋整?可以用 for in 遍历出来,当然也可以用ES6的Object.keys方法。

let person ={
 name:"ms",
 age:26
} ;

let keys=Object.keys(person)
console.log(keys);// ['name','age']

接下来我们使用下 enumerable 特性

let person ={
 name:"ms",
 age:26
} ;
Object.defineProperty(person,"sex",{
 value:"男",
 enumerable:true
})
Object.defineProperty(person,"birth",{
 value:"1995-11-24",
 enumerable:false
})
let keys=Object.keys(person)
console.log(keys);// ['name','age','sex']

enmmmmmmmmm,这个属性的意思就是,我让不让你被遍历出来啊。

configurable【可配置的】,这个就奇妙了,根据学习是有两个效果

  • 属性是否可以被删除
  • 属性的特性在第一次设置之后可否被重新定义特性
let person ={
 name:"ms",
 age:26
} ;
Object.defineProperty(person,"sex",{
 value:"男",
 configurable:false
})
delete person.sex; //无效,删除不了
console.log(person);

并且报错

Cannot delete property 'sex'

let person ={
 name:"ms",
 age:26
} ;
Object.defineProperty(person,"sex",{
 value:"男",
 configurable:true
})
delete person.sex; 
console.log(person);//{name: 'ms', age: 26}

我们删除了之后,重新定义一下

let person ={
 name:"ms",
 age:26
} ;
Object.defineProperty(person,"sex",{
 value:"男",
 configurable:true
})
delete person.sex; 
console.log(person);//{name: 'ms', age: 26}
Object.defineProperty(person,"sex",{
 value:"男",
 configurable:true
})
console.log(person);//{name: 'ms', age: 26, sex: '男'}

接下来是两个比较重要的特性 set get。啊,这,获取and设置。enmmm啥意思呢?

get

let person ={
 name:"ms",
};
let count = 26
Object.defineProperty(person,"age",{
  get:()=>{
    return count;
  }
})
console.log(person);//{name: 'ms'}

嗯?怎么回事 ? age 去哪里了 ?

let person ={
 name:"ms",
};
let count = 26
Object.defineProperty(person,"age",{
  get:()=>{
    return count;
  }
})
console.log(person.age);//26

这样才有,这是为啥呀?(还在疑惑,不知道为啥,求指教)

咦~但是这玩意儿好像Vue的computed计算属性!!!并且computed也有 get set

那我们换个方式

let person ={
 name:"ms",
};
let count = 26
Object.defineProperty(person,"age",{
  get:()=>{
    return count+1;
  }
})
console.log(person.age);//27

嗯 ,就是这样

set

let person ={
 name:"ms",
};
let count = 26
Object.defineProperty(person,"age",{
  get:()=>{
    return count;
  },
  set:(newVal)=>{
    count=newVal;
  }
})
console.log(person.age);//26
console.log(count);//26
person.age=30;
console.log(person.age);//30
console.log(count);//30

那继续,我们跟get一样,

let person ={
 name:"ms",
};
let count = 26
Object.defineProperty(person,"age",{
  get:()=>{
    return count;
  },
  set:(newVal)=>{
    count=newVal+8;
  }
})
console.log(person.age);//26
console.log(count);//26
person.age=30;
console.log(person.age);//38
console.log(count);//38

enmmmm也可以。

使用了get或set方法,那我设置value特性会怎么样呢?

let person ={
 name:"ms",
};
let count = 26
Object.defineProperty(person,"age",{
  get:()=>{
    return count;
  },
  set:(newVal)=>{
    count=newVal+8;
  },
  value:20,
})
...

报错啦!!

Invalid property descriptor. Cannot both specify accessors and a value or writable attribute

在报错中,看到不可以去指定 valuewritable 的值。这也就是说 writable 也会出现相同报错。

到这里也差不多了 。总结下

  • value: 设置属性的值
  • writable: 值是否可以重写。true | false
  • enumerable: 目标属性是否可以被枚举。true | false
  • configurable: 目标属性是否可以被删除或是否可以再次修改特性 true | false
  • set: 目标属性设置值的方法
  • get:目标属性获取值的方法

但是呢发现了一个小问题。为什么我使用 get 但是我打印对象后,却并不在其中,但是使用 . 去获取这个值,可以打印出来

let person ={
 name:"ms",
};
let count = 26
Object.defineProperty(person,"age",{
  get:()=>{
    return count;
  }
})
console.log(person);//{name: 'ms'}

age 去哪了呢,这种现象又是啥呢,好吧,我还不知道。等下去寻求下大佬。