1. 理解Proxy和Reflect
使用Proxy可以创建一个代理对象,允许对被代理对象的基本操作进行拦截并重新定义。
const proxyData = new Proxy(data, {
get(target, key, receiver) {
…
},
set(target, key, newVal, receiver) {
…
}
})
如上代码所示,Proxy对象接受两个参数,一个是data对象,第二个是参数是个对象,这个对象中包含了各种函数,比如其中的get用来拦截数据的读取操作,set用来拦截数据的赋值操作。
在js中函数也是一种对象,函数的调用也可以使用Proxy拦截器中apply函数来拦截。
const proxyFn = new Proxy(fn, {
apply(target, that, arg) {
…
return target.call(that, ...arg)
}
})
理解了Proxy,再来看下Reflect,它是一个全局对象,Proxy拦截器中的方法都能在Reflect中找到同名的方法,其实它就是提供了一个对象操作属性的默认行为。
const d = {
a: 1
}
d.a
Reflect.get(d, 'a')
上述两种读取数据的方式是等价的,那Reflect存在的意义又是什么呢?
其实Reflect.get还可以接收第三个参数receiver,可以理解为函数调用过程中的this,以下这行事例代码读取到的值不是1而是2。
Reflect.get(d, 'a', { a: 2 })
其实Reflect还有更多方面的意义,我们只讨论这个特点,是因为它与响应式数据的实现密切相关。
2. 响应式数据的实现与Reflect的关系
在之前的代码基础上运行以下代码
const origin = {
a: 1,
b: 2,
c: 3,
get d() {
return this.a
}
}
const data = reactive(origin)
effect(() => {
console.log(data.d)
})
data.a++
当data.a改变时副作用函数会响应吗?答案是不会,原因就在于这个this,当Proxy拦截到data.d的get操作时访问了this.a,这个this指向的其实是原始对象origin的a属性,而origin并不是响应式的对象。原因找到了,再来考虑如何解决问题,如果让this.a最终访问到data.a问题不就解决了嘛。Proxy拦截器可接收的参数中有一个receiver,它代表的就是当前的代理对象,再配合Reflect对象的对应方法,将reactive方法稍加改造就ok啦。
const reactive = (data) => {
return new Proxy(data, {
get (target, key, receiver) {
track(target, key)
return Reflect.get(target, key, receiver)
},
set (target, key, newValue, receiver) {
const res = Reflect.set(target, key, newValue, receiver)
trigger(target, key)
return res
}
})
}