[toc]
为了更好的学习本章节的内容,我们先来重温一下之前学过的知识。
### [回顾]属性描述符
#### 什么是属性描述符
该节中回顾的内容并非ES6中的知识,而是在ES6之前就已存在的JS知识。
Property Descriptor 属性描述符:是一个普通对象,相当于一个对象中某个属性的一些描述信息的集合,用于描述一个属性的相关信息
#### 如何获取一个对象的属性描述符?
通过```Object.getOwnPropertyDescriptor(对象, 属性名)```可以得到一个对象的某个属性的属性描述符
- value:属性值
- configurable:该属性的描述符是否可以修改
- enumerable:该属性是否可以被枚举
- writable:该属性是否可以被重新赋值,即该属性的value属性值是否可以被修改。
> ```Object.getOwnPropertyDescriptors(对象)```可以得到某个对象的所有属性描述符
#### 如何修改对象的属性描述符?
如果需要为某个对象添加属性时 或 修改属性时, 配置其属性描述符,可以使用下面的代码:
```js
const obj = {
a:33,
b:44
}
// Object.defineProperty(对象, 属性名, 描述符);设置对象的某个属性的属性描述符内容
Object.defineProperty(obj, 'b', {
value: 100, //执行完该条语句后对象obj的属性b的值变为100
writable: false //执行完该条语句后value的值就不能再被修改了
})
obj.b = 80 //因为上面设置了属性描述符writable为false,所以该条语句执行会报错。
//Object.defineProperties(对象, 多个属性的描述符);可以同时设置对象内多个属性的属性描述符
Object.defineProperties(obj,{
a:{
value:55,
writable: true
},
b:{
value:100,
writable: true
}
})
```
#### 存取器属性-属性描述符中较特殊的属性
属性描述符中,如果配置了 get 和 set 中的任何一个,则该属性,不再是一个普通属性,而变成了存取器属性。
get 和 set配置均为函数,如果一个属性是存取器属性,则读取该属性时,会运行get方法,将get方法得到的返回值作为属性值;如果给该属性赋值,则会运行set方法。
存取器属性实例应用:
```js
//应用场景,假设有一个学生信息记录系统,要求年龄输入时如果小于8岁或大于14岁就会提示输入错误,可以使用存取器来实现
const student = {
name: 'abc'
}
Object.defineProperty(student, 'age', { //此时我们将age属性定义为存取器属性
set(val){ //age属性设置了set以后,每次给age属性赋值时就会执行该函数
if(val >=8 || val <= 14){
student._age = val //因为存取器属性的特殊性,我们在赋值的时候用_age属性来代替age属性。
}else{
console.log('请输入年龄在8-14岁之间的值')
}
},
get(){ //age属性设置了get以后,每次读取age的值的时候会持行该函数
return student._age //同样在读取age的值的时候我们也用_age属性的值来代替,相当于用age存取器set和get函数来操作_age属性的值。
}
}
student.age = -100; //给age属性赋值会调用set函数,此时检测到-100小于8,根据set代码执行结果:系统会显示提示信息。运用此方法还可以判断数据类型的输入是否正确或增加更多的操作功能。
```
>存取器属性最大的意义,在于可以控制属性的读取和赋值。很多DOM元素中的属性使用的都是存取器属性,例如innerHTML
### Reflect
#### Reflect是什么?
Reflect是一个内置的JS对象,它提供了一系列方法,可以让开发者通过调用这些方法,访问一些JS底层功能
由于它类似于其他语言的**反射**,因此取名为Reflect
#### 它可以做什么?
使用Reflect可以实现诸如 属性的赋值与取值、调用普通函数、调用构造函数、判断属性是否存在与对象中 等等功能
#### 为什么需要用Reflect实现?
有一个重要的理念,在ES5就被提出:减少魔法、让代码更加纯粹
这种理念很大程度上是受到函数式编程的影响
ES6进一步贯彻了这种理念,它认为,对属性内存的控制、原型链的修改、函数的调用等等,这些都属于底层实现,属于一种魔法,因此,需要将它们提取出来,形成一个正常的API,并高度聚合到某个对象中,于是,就造就了Reflect对象
因此,你可以看到Reflect对象中有很多的API都可以使用过去的某种语法或其他API实现。
#### Reflect提供了哪些API?
- Reflect.set(target, propertyKey, value): 设置对象target的属性propertyKey的值为value,等同于给对象的属性赋值
- Reflect.get(target, propertyKey): 读取对象target的属性propertyKey,等同于读取对象的属性值
- Reflect.apply(target, thisArgument, argumentsList):调用一个指定的函数,并绑定this和参数列表。等同于函数调用
- Reflect.deleteProperty(target, propertyKey):删除一个对象的属性
- Reflect.defineProperty(target, propertyKey, attributes):类似于Object.defineProperty,不同的是如果配置出现问题,返回false而不是报错
- Reflect.construct(target, argumentsList):用构造函数的方式创建一个对象
- Reflect.has(target, propertyKey): 判断一个对象是否拥有一个属性
- 更多Reflect的API查阅:https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Reflect
### Proxy 代理
代理的功能:提供了修改底层实现的方式
如何建立一个对象的代理
```js
//代理一个目标对象
//target:目标对象
//handler:是一个普通对象,其中可以重写底层实现
//返回一个代理对象
const proxy = new Proxy(target, handler);
```
代理是将target目标对象包裹了一层返回一个新的对象,这样所有针对目标对象的操作需要通过代理对象来实现。(有点类似于当事人和律师的关系,各类询问均与律师交流,律师再将相关的结果转告当事人)
建立代理对象的handler参数是一个对象,对象中可以设置一些方法,用来改变操作对象的一些规则。Proxy代理在handler中可以改变Reflect反射中的各种底层实现。
例如,有这样一个需求:在使用代理来读取一个对象的某个属性值时,如果对象具有该属性,则返回属性值,如果不具有该属性返回false。我们就可以用代理来实现。
```js
const student = {
name:'aaa',
age: 20,
class: 3
}
const std = new Proxy (student, {
get(target, propertyKey) { //在代理的handler中新建一个get方法
if (Reflect.has(target, propertyKey)) { //handler中尽量使用Reflect反射中底层方法来实现,方便代码阅读。本条语句用Reflect来判断对象是否具有该属性,相当于(propertyKey in target)
return Reflect.get(target, propertyKey); //如果存在属性,用Reflect的get方法返回属性值
} else {
return false; //若不存在返回false
}
},
})
//用了代理以后,我们针对student对象操作属性时使用std代理对象来操作。
console.log(std.age); //返回20
console.log(std.address); //返回false
```
>灵活运用代理,可以为操作对象制定很多更加灵活的规则。其功能类似于属性描述符的存取器属性。