一、vue3源码中为什么Proxy需要搭配Reflect来实现响应式?

1、reactive的核心逻辑如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
import { isObject } from '@vue/shared'
const mutableHandlers = {
get(target, key, receiver) {
return Reflect.get(target, key, receiver)
},
set(target, key, value, receiver) {
Reflect.set(target, key ,value, receiver)
return true
}
}
export function reactive(target) {
if (!isObject(target)) return
const proxy = new Proxy(target, mutableHandlers)
return proxy
}

2、在get和set中,使用了Reflect的get,set方法,那为什么不直接用target[key]呢,效果不是一样的么?

看起来是这样,但是在一些情况下,就能看到明显的问题。我们先举个例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
let obj = {
name: 'zhangsan',
get nickName{
return 'nickName:' + this.name
}
}
// obj中的nickName通过this调用了name,而导致name没有被依赖收集(详情看下面解释)
let proxyObj = new Proxy(obj, {
get(target, key, receiver) {
console.log('收集依赖:', key)
return target[key]
}
})
// 进行取值操作
console.log(proxyObj.nickName)

上述代码中,是一个很简单的代理,如果我们在页面中,使用了proxyObj.nickName这个取值代码,那么根据相应逻辑,执行代码打印的结果就是:

1
2
收集依赖: nickName
nickName:zhangsan

那么很明显的问题就是,obj中的name属性,没有被依赖收集,那么如果在后续操作中,我们对proxyObj.name = ‘xxxxxx’进行赋值了,因为没有被依赖收集到,所以虽然数据变化了,但是页面视图却并没有同步发生变化。说到底还是因为this指向的原因,当前this指向了obj,而我们希望这个this指向被代理后的proxyObj,这样才能够将name属性也收集到,那么所以,我们此时应该使用Reflect,来使this正确的指向被代理后的proxyObj属性。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
let obj = {
name: 'zhangsan',
get nickName() {
return 'nickName:' + this.name
}
}
let proxyObj = new Proxy(obj, {
get(target, key, receiver) {
console.log('收集依赖:', key)
return Reflect.get(target, key, receiver)
}
})
// 进行取值操作
console.log(proxyObj.nickName)

经过此番修改,我们再执行代码,会发现,诶name属性也被成功的进行依赖收集了,达到了我们的预期.这就是为什么这里要使用Reflect的原因啦。

1
2
3
收集依赖: nickName
收集依赖: name
nickName:zhangsan

3、详细了解Reflect

见ES6+/《ES6中的Reflect》