用小白都能看得懂的方式述说 对 Vue 的一些深入理解(面试适用)

Vue Watch中的 deep:true 是如何实现的

想象一下你有一个大盒子(对象或数组),里面装了很多小盒子(对象的属性或数组的元素)。当你只关注大盒子本身时,你并不知道里面的小盒子发生了什么变化。但是,如果你非常好奇,想要知道里面每一个小盒子的变化,那么你就需要一种方法来“深入”观察这个大盒子。

在 Vue.js 中,watch 就像是一个观察者,它可以帮助你监视某个数据的变化。但是,默认情况下,watch 只观察大盒子本身的变化,比如整个对象被替换了,或者整个数组被修改了。它不会深入到里面去看小盒子(属性或元素)发生了什么。

这时候,deep: true 就像一个放大镜,它告诉 Vue.js 的 watch:“我不仅要观察大盒子的变化,我还要深入到每一个小盒子里去,看它们发生了什么变化。”

在 Vue 2 中,这个“深入观察”的实现方式是,Vue 会遍历大盒子里的每一个小盒子,然后给它们每一个都加上一个“小标签”(getter 和 setter。这样,每当任何一个小盒子里的内容发生变化时,这个小标签就会告诉 Vue.js:“嘿,这里有个变化!”然后 Vue.js 就可以执行你指定的 watch 回调函数了。

但是,这种方式有一个问题,就是如果大盒子里的小盒子非常多,或者小盒子里面还有更多的小盒子(深层嵌套的对象),那么给它们每一个都加上“小标签”就会消耗很多的时间和资源

到了 Vue 3,Vue.js 使用了一种更先进的方法来实现“深入观察”,它叫做 Proxy。Proxy 可以让你直接在大盒子上放一个“超级标签”,这个标签会自动监视大盒子里所有小盒子的变化,而不需要你给每一个小盒子都单独加标签。这样,Vue.js 就可以更高效地实现 deep: true 的功能了。

所以,简单来说,deep: true 就是让 Vue.js 的 watch 能够“深入”观察对象或数组内部的每一个小变化,它背后的实现原理是通过递归地为每个属性或元素添加监视器(Vue2)或使用 Proxy 来实现更高效的全局监视(Vue3)

1)源码解析:

// 用于遍历一个对象或数组中的所有属性或元素
function _traverse (val: any, seen: SimpleSet) {
  let i, keys
  const isA = Array.isArray(val)

  /* 1、类型检查:
    首先,检查传入的值 val 是否是数组或对象,并且没有被冻结(Object.isFrozen(val)),
    也不是Vue 的虚拟节点(VNode)。如果不是这些类型之一,则直接返回。 */

  if ((!isA && !isObject(val)) || Object.isFrozen(val) || val instanceof VNode) {
    return
  }

  /* 2、处理已观察的对象:
    如果 val 是一个已经被 Vue 观察的对象(即 val.__ob__ 存在),则检查它是否已经在 seen 集合中
    出现过。这是为了避免无限循环和重复观察。*/

  if (val.__ob__) {
    const depId = val.__ob__.dep.id
    if (seen.has(depId)) {
      return
    }
    seen.add(depId)
  }

 /* 3、遍历:
   如果 val 是数组,则使用逆序遍历(从最后一个元素开始)来遍历数组中的每一个元素,
   并对它们调用 _traverse 函数。
   如果 val 是对象,则获取对象的所有键,并同样使用逆序遍历来遍历这些键对应的值,
   并对它们调用 _traverse 函数。*/

  if (isA) {
    i = val.length
    while (i--) _traverse(val[i], seen)
  } else {
    keys = Object.keys(val)
    i = keys.length
    while (i--) _traverse(val[keys[i]], seen)
  }
}

/* 下面的 get 函数,是 Vue.js 中响应式系统的一部分,用于在访问一个属性时执行一系列操作。
   这里的 get 函数特别与 watcher 相关,因为它在 watcher 被创建时用来获取被观察属性的值。*/

get () {
    /* 1、pushTarget(this):
    这一行将当前的 watcher 实例设置为全局的 Dep.target。
    在 Vue.js 中,Dep.target 是一个栈,用于记录当前正在进行的依赖收集的目标 watcher。
    这样做是为了在属性的 getter 被调用时,能够将这个 watcher 添加到属性的依赖列表中。*/

    pushTarget(this) // 先将当前依赖放到 Dep.target上
    let value
    const vm = this.vm
    try {

       /* 2、获取值:
        通过调用 this.getter.call(vm, vm) 来获取被观察属性的值。
        这里的 getter 是一个函数,它会在创建 watcher 时被设置,用于访问实际的属性值。
        vm 是 Vue 实例,这里将它作为 getter 函数的上下文(this)和参数传入。*/

      value = this.getter.call(vm, vm)
    } catch (e) {

       /* 3、异常处理:
        如果在获取值的过程中发生异常,会根据 this.user 的值来决定是否捕获这个异常。
        如果是用户定义的 watcher(this.user 为 true),则会调用 handleError 函数来处理异常;
        否则,直接抛出异常。*/

      if (this.user) {
        handleError(e, vm, `getter for watcher "${this.expression}"`)
      } else {
        throw e
      }
    } finally {

       /* 4、异常处理:
        如果 this.deep 为 true,则表示需要进行深度观察。
        此时,会调用 _traverse 函数来遍历获取到的值(无论是一个对象还是数组),
        并对其中的每一个属性或元素执行相同的操作(即再次调用 get 方法)。
        这样,就可以确保对象或数组内部的所有嵌套属性都被观察到。*/

      if (this.deep) { // 如果需要深度监控
        traverse(value) // 会对对象中的每一项取值,取值时会执行对应的get方法
      }
       /* 5、popTarget():
        最后,将 Dep.target 栈顶的元素弹出,恢复之前的状态。*/

      popTarget()
    }
    return value
}

为何 Vue 采用异步渲染

如果不采用异步更新,那么每次更新数据都会对当前组件进行重新渲染。所以为了性能考虑,Vue会在本轮数据更新后,再去异步更新视图

举个例子:假如你正在画画,但是你的颜料还没干,每次画完一笔都要等它干了才能继续画下一笔。这就像同步

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值