返回

Vue3响应式系统源码解析-Effect篇

前端

在上一篇中,我们介绍了 Vue3 响应式系统的基本原理和实现,以及如何劫持数据。但还有两个大问题一直没解决,即具体是如何收集依赖,又是如何触发监听函数的。

从前文中,我们大致能猜到:向 effect 函数传递一个原始函数,会创建一个监听函数,并且会立即执行一次。而第一次执行时,就能通过读操作中的 track 函数将当前正在读取的属性收集起来,形成依赖关系。

但是,这仅仅是我们猜测,我们还是来细致地看看源码吧。

依赖收集

依赖收集的原理

Vue3 的依赖收集原理非常巧妙,它利用了 JavaScript 的 Proxy 对象。Proxy 对象可以劫持一个对象的属性访问操作,并在访问时执行我们自己的代码。

在 Vue3 中,每个响应式对象都被一个 Proxy 对象包裹起来。这个 Proxy 对象会劫持对象的属性访问操作,并在访问时收集依赖关系。

例如,当我们访问一个响应式对象的属性时,Proxy 对象会执行以下步骤:

  1. 检查该属性是否已经被劫持。如果没有,则创建一个新的监听函数并将其与该属性关联。
  2. 将当前正在执行的 effect 函数添加到该监听函数的依赖数组中。
  3. 返回属性的值。

这样,我们就实现了依赖的收集。

依赖收集的实现

在 Vue3 中,依赖的收集是在 track 函数中实现的。track 函数接受两个参数:一个目标对象和一个属性名。它会检查该属性是否已经被劫持,如果没有,则创建一个新的监听函数并将其与该属性关联。然后,将当前正在执行的 effect 函数添加到该监听函数的依赖数组中。

function track(target, key) {
  let dep = targetMap.get(target)
  if (!dep) {
    dep = new Dep()
    targetMap.set(target, dep)
  }

  let depIds = dep.depIds
  let effect = activeEffect
  if (effect) {
    let id = effect.id
    if (depIds.has(id)) {
      return
    }
    depIds.add(id)
    effect.deps.push(dep)
  }
}

触发监听函数

触发监听函数的原理

Vue3 的监听函数触发原理也非常巧妙,它利用了 JavaScript 的事件循环。

在 Vue3 中,每个监听函数都被保存在一个队列中。当响应式对象的属性发生变化时,Vue3 会将该属性的监听函数添加到队列中。然后,在下一个事件循环中,Vue3 会依次执行队列中的监听函数。

这样,我们就实现了监听函数的触发。

触发监听函数的实现

在 Vue3 中,监听函数的触发是在 trigger 函数中实现的。trigger 函数接受一个目标对象和一个属性名作为参数。它会获取该属性的监听函数队列,然后依次执行队列中的监听函数。

function trigger(target, key) {
  let dep = targetMap.get(target)
  if (!dep) {
    return
  }

  let depIds = dep.depIds
  depIds.forEach((id) => {
    let effect = effectMap.get(id)
    if (effect) {
      effect.run()
    }
  })
}

总结

以上就是 Vue3 响应式系统中依赖收集和监听函数触发的原理和实现。通过这两个机制,Vue3 可以实现响应式数据的变化跟踪和监听函数的自动执行,从而实现响应式系统。