和Vue3和解的Day4--vue3的优势和底层原理

93 阅读4分钟

开启掘金成长之旅!这是我参与「掘金日新计划 · 2 月更文挑战」的第 4 天,点击查看活动详情

说点题外话

上一篇说了vue2使用Object.defineProperty作为底层原理的缺点,这一篇就看看vue3如何解决这个缺点的

说正文

image.png

先回顾一个Vue2的缺点:数组或者对象新增的属性和索引都无法监听,需要手动重新触发侦听

vue3的Proxy和vue2的Object.defineProperty

  • vue2是给每个对象添加一个getter和setter属性去改变对象,实现对数据的检测。
  • Object.defineProperty和Proxy的本质区别是:defineProperty只对属性进行劫持,所以出现了需要递归遍历,新增属性需要手动重新侦听的问题。
  • Proxy代理的是整个对象(侦听的是整个对象或数组而不是属性), 并且一开始只代理最外层的对象,性能更好。Proxy内置了13种拦截方法,是definePorperty没有的优势。

那你肯定还会有很多疑问?

1. Proxy如果只侦听最外层,那么Vue3怎么实现震度侦听呢?

判断当前Reflect.get的返回值是否还是个对象,如果是对象那么就继续执行代理,用来实现深度侦听。

Proxy详解

我们知道了proxy的优势,那么它是如何使用呢

const p = new Proxy(target, handler)

  • target: 要使用 Proxy 包装的目标对象(可以是任何类型的对象,包括原生数组,函数,甚至另一个代理)。

  • handler: 一个通常以函数作为属性的对象,各属性中的函数分别定义了在执行各种操作时代理 p 的行为。

我举个例子验证一下proxy是如何实现侦听的

    const person = { name: "小白" }

    const proxy = new Proxy(person, {
      get(target, key, receiver) {
        console.log("target: ", target, "key:", key, "receiver:", receiver)
      },
      set(target, key, value, receiver) {
        console.log("target: ", target, "key:", key, "value:",value, "receiver:", receiver)
      }
    })
proxy.name

此时的输出结果是target: {name: '小白'} key: name receiver: Proxy {name: '小白'}, 触发了get方法。

proxy.name = "小刚"

当我们改变name的属性值后,输出结果是:target: {name: '小白'} key: name value: 小刚 receiver: Proxy {name: '小白'}

这里就可以看出来proxy的强大了,毕竟这是Object.defineProperty没做到的事情。

proxy的强大不止于此,它一般和Reflect搭配使用。

    const person = { name: "小白" }

    function observe(target) {

      return new Proxy(target, {
        get(target, key, receiver) {
          console.log(`访问了${key}属性`)
          return Reflect.get(target, key, receiver)
        },
        set(target, key, value, receiver) {
          console.log(`${key}由->${target[key]}->设置成->${value}`)
          Reflect.set(target, key, value, receiver)
        }
      })
    }

    const proxy = observe(person)
console.log(proxy.name)

输出结果是

image.png

这里和不使用reflect一样直接获取属性的值会触发get方法,但是reflect会返回一个value值

    proxy.name = '小刚'
    console.log(proxy.name)

这里的输出结果是

image.png

不仅触发了get方法也触发了set方法进行更新。以上proxy体现出来的和defineProperty差不多,那么如果是新增的属性呢

    proxy.age = 18
    console.log(proxy.age)

image.png

当新增一个对象属性之后,会同时触发get和set方法,这一点与defineProperty就不同了。

3. Reflect

在说说为什么Proxy搭配Reflect使用,先看看Reflect方法是怎么用的,这里就说说上面使用的get方法和set方法

我们也可以理解为reflect.get(target, key, receiver) 中的receiver在target指定getter的情况下是调用getter的this值。当我们访问target的key值时,this是指向receiver的,所以实际上我们访问的是receiver的key值,但是这可不是直接访问receiver[key]

const person = { name: '小白' }

const proxy = new Proxy(person, {
    get(target, key, receiver) {
        return Reflect.get(receiver, key)
    },
    set(target, key, value, receiver) {
        Reflect.set(receiver, key, value)
    }
})

console.log(person.name)

proxy.name = '小刚' 

这段代码输出结果是

image.png

因为 Reflect.get(receiver, key)相当于receiver[key], 又会触发get方法直接陷入死循环。

image.png

说再见

下一篇就详细说说MVVM响应式原理,面试题直接开业大酬宾

难忘今宵

为什么有些人爱抖腿?

抖腿这种行为可分为两种情况:一是日常抖腿,属于人在生活中不自觉养成的习惯;二是属于疾病性抖腿,就算腿的主人想停止,但却无法控制自己的抖腿行为。