Vue是用proxy代理对象读和写的操作,我们不能纯粹拦截get和set操作,比如还有拦截for...in?track函数如何追踪拦截for..in操作,还有拦截Map,Set,WeakMap,WeakSet,对于非原始值(引用数据类型)的代理还需要更多考虑 首先,要考虑那么多非原始值操作,首先要理解Proxy和Reflect
1.理解Proxy和Reflect
Proxy最基本的功能,就是代理对象,拦截对象的操作,最常用的就是get()和set()读和写.
这里的代理指的就是对象基本语义的代理,允许我们拦截并重新定义一个对象的基本操作.
什么又是基本语义,比如get(),set()这种对象的基本操作,还有下面拦截函数的调用也是基本操作
在JavaScript中,函数也是一个对象,所以Proxy也可以拦截函数的调用操作,拦截函数就是用Proxy中的apply拦截函数
const fn=(name)=>{
console.log('我是',name)
}
const p2 = new Proxy(fn,{
//使用apply拦截函数调用
apply(target,thisArg,argArray){
traget.call(thisArg,...argArray)
}
})
有基本操作那就有非基本操作,比如
obj.fn()
这种是要先set()进行读取操作,然后读取到了,再继续apply函数调用的操作,这种就叫做复合操作.知道proxy只能代理对象基本操作,后面对set,map进行代理,都是利用proxy这个特点.
理解了Proxy,接下来就是理解Reflect了,这个再官方的定义叫反射,我的理解就是把一些对象的操作反射出来,proxy能够找到的方法都能在Reflect找到.比如Reflect.get
const obj={foo:1}
//直接读取
console.log(obj.foo)
//使用Reflect读取
console.log(Reflect.get(obj,'foo'))
但是Reflect和Proxy不同的,它可以接受第三个参数,就是"指定接受者receiver",有点类似于this
const person = {
firstName: "John",
lastName: "Doe",
get lastName(){
return this.lastName
}
};
const fullName = Reflect.get(person, "firstName") + " " + Reflect.get(person, "lastName",{lastName:'555'});
console.log(fullName); //John 555
之前在实现响应式系统文章中,proxy是这样为对象进行代理
const obj = {foo:1}
const p = new Proxy(obj,{
get(target,key){
track(target,key){
//没有用Reflect.get读取
return traget(key)
}
},
set(target,key,newVal){
//没有使用Reflect.set完成设置
target[key]=newVal
trigger(target,key)
}
})
之前的响应式系统在这里就会有问题了
const obj = {
foo:1,
get bar(){
return this.foo
}
}
然后我们在副作用函数中访问bar属性
effect(()=>{
console.log(p.bar)
})
在这里,我们执行了getter,访问了this.foo,副作用函数应该会和foo产生联系,但是我们修改这个值
p.foo++
副作用函数并不会执行,问题就出现在访问器函数中的this,在Proxy的get函数中,通过target[key]返回函数值的,这里的target是代理对象的原始对象,也就是obj,target[key]就相当于obj.bar,我们用p.bar访问属性时,getter访问的this是原始对象obj,最终访问的也是obj.foo,只有访问p.foo才能和副作用函数进行类型,进行依赖收集.这时,Reflect就是来解决这个问题的
const p = new Proxy(obj,{
get(target,key,receiver){
track(target,key)
//使用Reflect.get读取,返回读取到的属性值
retuen Reflect.get(target,key,receiver)
}
})
这里的receiver就是代理对象p,使用Refl.get传入第三个参数,改变访问器函数作用域,将this改为代理对象,这样就成功访问到代理对象p,就可以进行依赖收集