• Vue源码阅读【番外篇】:为什么Proxy需要搭配Reflect来实现响应式?


    前言

    我们都知道vue3.x版本是通过 Proxy 来实现的,用以解决2.X的一些缺陷。

    但是我最近通过看 vue 源代码发现它内部很多地方是 Proxy、Reflect搭配一起使用的,例:

    image.png image.png image.png 我们通过上面几张图可以看到 get、set、deleteProperty、has、ownKeys都用了 Reflect

    基于此,这就是我写这篇文章的缘由,那我们探索一下为什么要搭配这两使用。

    ProxyReflect 概念

    • Proxy: 代理,可以通过代理对象完成对目标对象的拦截,并在拦截后进行过滤和改写等操作,支持的拦截操作共 13
    • Reflect: 反射,它提供拦截 JavaScript 操作的方法。这些方法与 Proxy 的方法一一对应,也是 13 种。

    Proxy简单使用示例:

    const person = {
        name: '张三',
        get nickName() {
            return `${this.name}是坏蛋`
        }
    }
    const personProxy = new Proxy(person, {
        get(target, key, receiver) {
            console.log('进来了吧') 
            return target[key]
        }
    })
    console.log(personProxy.nickName)
    // 先打印了:进来了吧
    // 然后打印了:张三是坏蛋 
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15

    通过上面这个示例,我们发现他成功代理了,虽然我们没做什么处理。

    不过 get 里面的 receiver 这个参数我们还没用到,receiver在这个时候表示代理对象(也就是personProxy)。

    我们在 get 里面的return target[key],不能return receiver[key],否则会死循环。

    我们再来个 Proxy 示例2:

    const person = {
        name: '张三',
        get nickName() {
            console.log(this)
            return `${this.name}是坏蛋`
        }
    }
    
    const personProxy = new Proxy(person, {
        get(target, key, receiver) {
            return target[key]
        }
    })
    
    const person2 = {
        name: '李四'
    }
    
    Object.setPrototypeOf(person2, personProxy)
    
    console.log(person2.nickName) 
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21

    上面这个示例种的console.log(this)console.log(person2.nickName) 会打印上面呢?

    我们希望的应该是 person2这个对象李四是坏蛋 是吧,但实际上没有如期打印。

    首先person2上面没有nickName这个属性,所以他会去personProxy上面找,然后返回了 person 上面的 nickName属性,nickName中的this指向了person他自己。

    所以console.log(this)打印出来的是 person 对象,console.log(person2.nickName)打印出来的是person中的name,也就是张三是坏蛋

    那我们怎么才能让他按照我们设想的那样打印李四是坏蛋

    我们来到第三个示例:

    const person = {
        name: '张三',
        get nickName() {
            console.log(this)
            return `${this.name}是坏蛋`
        }
    }
    
    const personProxy = new Proxy(person, {
        get(target, key, receiver) {
            return Reflect.get(target, key, receiver)
        }
    })
    
    const person2 = {
        name: '李四'
    }
    
    Object.setPrototypeOf(person2, personProxy)
    console.log(person.nickName)        // 张三是坏蛋
    console.log(personProxy.nickName)   // 张三是坏蛋
    console.log(person2.nickName)       // 李四是坏蛋
    /*
    *   三次this打印的顺序如下:
    *   { name: '张三', nickName: [Getter] }
    *   { name: '张三', nickName: [Getter] }
    *   { name: '李四' }
    * */ 
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28

    我们通过在 get 里面 return Reflect.get(target, key, receiver) 来实现按我们想要的那样打印出来。

    我们给 Reflectget 传递第三个参数(Proxy中的receiver),然后他就会修改调用时的this指向(也就是把指向person修改为:指向 person2)。

    我们引用阮老师中的 Reflect 代码片段来说明一下 receiver,例:

    var myObject = {
      foo: 1,
      bar: 2,
      get baz() {
        return this.foo + this.bar;
      },
    };
    
    var myReceiverObject = {
      foo: 4,
      bar: 4,
    };
    
    Reflect.get(myObject, 'baz', myReceiverObject) // 8 
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14

    如果name属性部署了读取函数(getter),则读取函数的this绑定receiver

  • 相关阅读:
    Vue框架--Vue列表渲染(1)
    酷开科技丨酷开系统智慧中心,解锁AI智能家居生活的无限可能
    神经网络与深度学习-9- 网络结构 -PyTorch
    设计模式 (原则)
    网络基础(网络层)
    Springboot整合Mybatis
    WZOI-207双胞胎数
    HarmonyOS ArkUI实战开发-NAPI 加载原理(上)
    web前端期末大作业:基于HTML+CSS+JavaScript奥迪企业bootstrap响应式网站
    如何通俗理解逻辑回归(Logistic Regression)
  • 原文地址:https://blog.csdn.net/qq_53225741/article/details/125510252