ZiuChen.github.io/docs/article/深入理解Proxy与Reflect.md
2023-02-05 15:07:58 +08:00

280 lines
7.3 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 深入理解Proxy与Reflect
### 监听对象的操作
可以使用Proxy对象将原对象包裹此后的操作都对`proxy`进行,每次`get``set`被触发时都会自动执行相应代码
```js
const obj = {
name: 'ziu',
age: 18,
height: 1.88
}
const proxy = new Proxy(obj, {
get(target, key) {
console.log('get', key)
return target[key]
},
set(target, key, value) {
console.log('set', key, value)
target[key] = value
}
})
```
```js
const tmp = proxy.height // getter被触发
proxy.name = 'Ziu' // setter被触发
```
除此之外,在之前的版本中可以通过`Object.defineProperty`为对象中某个属性设置`getter``setter`函数,可以达到类似的效果
```js
for (const key of Object.keys(obj)) {
let value = obj[key]
Object.defineProperty(obj, key, {
get() {
console.log('get', value)
return value
},
set(newVal) {
console.log('set', key, newVal)
value = newVal
}
})
}
```
但是通过`Object.defineProperty`实现的监听存在问题:
- `Object.defineProperty`设计之初并不是为了监听一个对象中的所有属性的
- 如果要监听新增/删除属性,那么此时`Object.defineProperty`是无能为力的
### Proxy类基本使用
```JS
const proxy = new Proxy(target, handler)
```
即使不传入handler默认也会进行基本的代理操作
```js
const obj = {
name: 'ziu',
age: 18
}
const proxy = new Proxy(obj, {})
proxy.height = 1.88 // 添加新属性
proxy.name = 'Ziu' // 修改原属性
console.log(obj) // { name: 'Ziu', age: 18, height: 1.88 }
```
### 捕获器
常用的捕获器有`set``get`函数
```js
const proxy = new Proxy(obj, {
set: function (target, key, newVal) {
console.log(`监听: ${key} 设置 ${newVal}`)
target[key] = newVal
},
get: function (target, key) {
console.log(`监听: ${key} 获取`)
return target[key]
}
})
```
- set函数有四个参数
- target 目标对象(侦听的对象)
- property 即将被设置的属性key
- value 新属性值
- receiver 调用的代理对象
- get函数有三个参数
- target 目标对象(侦听的对象)
- property 被获取的属性key
- receiver 调用的代理对象
另外介绍两个捕获器:`has``deleteProperty`
```js
const proxy = new Proxy(obj, {
...
has: function (target, key) {
console.log(`监听: ${key} 判断`)
return key in target
},
deleteProperty: function (target, key) {
console.log(`监听: ${key} 删除 `)
return true
}
})
delete proxy.name // 监听: name 删除
console.log('age' in proxy) // 监听: age 判断
```
### Reflect
Reflect是ES6新增的一个API它本身是一个对象
- 提供了很多操作JavaScript对象的方法有点像Object中操作对象的方法
- 比如`Reflect.getPrototypeOf(target)`类似于`Object.getPrototypeOf()`
- 比如`Reflect.defineProperty(targetm propertyKey, attributes)`类似于`Object.defineProperty()`
如果我们又Object对象可以完成这些操作为什么还需要Reflect呢
- Object作为一个构造函数这些操作放到它身上并不合适
- 包含一些类似于 in delete的操作符
- 在ES6新增了Reflect让这些操作都集中到了Reflect对象上
- 在使用Proxy时可以做到不操作原对象
### 与Object的区别
删除对象上的某个属性
```js
const obj = {
name: 'ziu',
age: 18
}
// 当且仅当该属性的 configurable 键值为 true 时,该属性的描述符才能够被改变
// 同时该属性也能从对应的对象上被删除。 默认为 false。
Object.defineProperty(obj, 'name', {
configurable: false
})
// 1. 旧方法 检查`delete obj.name`是否执行成功
// 结果: 需要额外编写检查代码且存在问题(严格模式下删除configurable为false的属性将报错)
delete obj.name
if (obj.name) {
console.log(false)
} else {
console.log(true)
}
// 2. Reflect
// 结果: 根据是否删除成功返回结果
if (Reflect.deleteProperty(obj, 'name')) {
console.log(true)
} else {
console.log(false)
}
```
### Reflect常见方法
其中的方法与Proxy的方法是一一对应的一共13个。其中的一些方法是Object对象中没有的
- `has` 判断一个对象是否存在某个属性,和 `in` 运算符功能完全相同
- `get` 获取对象身上某个属性的值,类似于`target[key]`
- `set` 将值分配给属性的函数返回一个Boolean如果更新成功则返回true
- `deleteProperty` 作为函数的 `delete` 操作符,相当于执行 `delete target[key]`
- ···
代理对象的目的不再直接操作原始对象一切读写操作由代理完成。我们先前在编写Proxy的代理代码时仍然有操作原对象的行为
```js
const proxy = new Proxy(obj, {
set: function (target, key, newVal) {
console.log(`监听: ${key} 设置 ${newVal}`)
target[key] = newVal // 直接操作原对象
},
})
```
这时我们可以让Reflect登场代替我们对原对象进行操作之前的代码可以修改
```js
const proxy = new Proxy(obj, {
set: function (target, key, newVal) {
console.log(`监听: ${key} 设置 ${newVal}`)
Reflect.set(target, key, newVal)
},
get: function (target, key) {
console.log(`监听: ${key} 获取`)
return Reflect.get(target, key)
},
has: function (target, key) {
console.log(`监听: ${key} 判断`)
return Reflect.has(target, key)
}
})
```
使用Reflect替代之前的对象操作有以下好处
- 代理对象的目的:不再直接操作原对象
- Reflect.set方法有返回Boolean值可以判断本次操作是否成功
- receiver就是外层的Proxy对象
针对好处三,做出如下解释。以下述代码为例,`set name(){}`函数中的`this`指向的是`obj`
```js
const obj = {
_name: 'ziu',
set name(newVal) {
console.log(`set name ${newVal}`)
console.log(this)
this._name = newVal
},
get name() {
console.log(`get name`)
console.log(this)
return this._name
}
}
console.log(obj.name)
obj.name = 'Ziu'
```
```js
const proxy = new Proxy(obj, {
set: function (target, key, newVal, receiver) {
console.log(`监听: ${key} 设置 ${newVal}`)
Reflect.set(target, key, newVal, receiver)
},
get: function (target, key, receiver) {
console.log(`监听: ${key} 获取`)
return Reflect.get(target, key, receiver)
}
})
```
我们使用Proxy代理并且使用Reflect操作对象时输出的`this`仍然为`obj`,需要注意的是,此处的`this`指向是默认指向原始对象`obj`,而如果业务需要改变`this`指向,此时可以为`Reflect.set()`的最后一个参数传入`receiver`
### Reflect.construct方法
以下两段代码的实现结果是一样的:
```js
function Person(name, age) {
this.name = name
this.age = age
}
function Student(name, age) {
Person.call(this, name, age) // 借用
}
const stu = new Student('ziu', 18)
console.log(stu)
```
```js
function Person(name, age) {
this.name = name
this.age = age
}
function Student(name, age) {
// Person.call(this, name, age) // 借用
}
const stu = new Reflect.construct(Person, ['ziu', 18], Student)
console.log(stu)
```