灵魂拷问:为什么Vue3源码proxy要和reflect一起用?

50 阅读2分钟

前言

最近在网上冲浪无意看到了这个问题,我的第一反应就是直接一脸问号。据说这是一道字节面试题,所以就赶紧学习了一下。

先说结论,proxy和reflect解决了以下几个问题:

  • 契合性
  • 原子性
  • 一致性
  • 解决this指向问题

那么我们来逐一分析解决的这几个问题吧,首先来看一段代码:

// 原始对象
const obj={
    name:'张三'get sayMyName(){
        return this.name
    }
}
// 代理对象
const obj2=new Proxy(obj,{
    get(target,prop,receiver){
        return target[prop]
    }
    set(target,prop,value,receiver){
        return Reflect.set(target,prop,value,receiver)
    }
    ...
})
console.log(obj2.sayMyName)

在上述代码中,我们创建了一个obj对象和obj2代理obj。obj2可以代理obj的13种的方法,reflect也是与之对应的可以操作那13种方法,因此,这解决了第一个问题契合性,proxy和reflect有着一一对应的方法操作,而且参数也是一模一样的。

在拦截写操作(set方法)如果使用常规key-value写法的时候,我们需要给定一个返回值类型为boolean的返回值。但是,我们编写set方法时候返回值要么是true要么是false,而且一旦设置了就无法改变。如果我们设置为true时候,要是我们给它设置属性失败了,但是返回的结果又是true,这样就打破了原子性,所以这样操作是不对的。那么我们应该使用reflect了,reflect的返回值正好也是一个布尔值,与set方法是一致的。这样巧妙的解决了原子性问题和一致性问题。

请你思考一下上面的this指向的是谁?从上面编写代码上,感觉应该是想要通过obj2代理obj调用其sayMyName方法让它返回张三。我们运行一下上面那段代码也确实如此,this指向的就是obj,所以返回结果自然就是张三了。

那么接下有几个问题要考虑,在那处的this指向的是原始对象。我们原始对象可以收集依赖吗?很显然那些get、set方法是不在原始对象身上的,所以原始对象是不能收集依赖的。那么就应该用reflect的第三个参数receiver来指向代理对象自身来解决这个问题,通过此操作把this指向改变成代理对象obj2了,所以响应式就自然可以收集到了,同时也解决this指向问题。上面obj2的get函数就应该改正一下

const obj2=new Proxy(obj,{
    get(target,prop,receiver){
        return Reflect.get(target, prop, receiver)
    }
    ...
})

Ending

当我看完原文讲解的时候,感受到了一个大大的妙字。不知各位看完感受如何,要是面试被问到如此问题,你该如何回答呢?