带你从0开始了解vue3核心(computed, watch)

计算属性 computed

计算属性computed会基于其响应式依赖被缓存,并且在依赖的响应式数据发生变化时重新计算。

计算属性也是一个ref对象。

测试用例

js 复制代码
  <div id="app"></div>
  <script>
    const { computed, reactive, effect } = Vue;

    const obj = reactive({
      name: "zh"
    })


    const computedObj = computed(() => {
      return "执行" + obj.name
    })

    effect(() => {
      // 触发ComputedRefImpl 的 get value
      document.getElementById("app").innerHTML = computedObj.value
    })

    setTimeout(() => {
      obj.name = "llm"
    }, 2000)
  </script> 

我们先来介绍一下执行流程,再看断点调试。

  • 上来先执行computed,创建一个computedRef对象。(所有computed对象都是Ref对象)
  • 初始化computedRef对象时,创建一个ReactiveEffect对象。并将computed的getter函数传入。
  • 然后执行effect,创建ReactiveEffect对象,并将effect回调传入。
  • 然后computedObj.value触发computed对象的get value, 收集执行effect创建的ReactiveEffect对象。
  • 通过_dirty变量控制computed的getter执行,触发reactive对象的getter方法,收集依赖(收集的是创建computedRef对象时内部创建的ReactiveEffect对象)。(这里非常重要的一点,只要触发computed get value就有可能重新执行computed的getter)
  • 2s后,触发reactive对象的setter方法,触发依赖执行。这里就需要注意了。由于触发的是computedRef对象时内部创建的ReactiveEffect对象,上面挂载的有computed,并且有scheduler调度器,所以会先执行含有computed属性的依赖具有scheduler调度器的依赖。
  • 执行调度器,调度器中触发computed对象get value收集的依赖。此时document.getElementById("app").innerHTML = computedObj.value执行,又触发computed get value, 执行computed 的getter 方法,返回修改的值。

断点调试

  • 初始化computed
  • 触发computed get value,进行依赖收集,并执行computed传入的getter方法
  • 2s后触发reactive setter,然后触发依赖函数。此时该依赖有computed对象,所以调用scheduler调度器
  • 触发effect回调,又会触发computed对象的get value。获取最新值。这里需要注意,虽然2s后触发了reactive的setter方法,但是并没有在trigger中直接执行computed的getter函数,而是通过再次触发computed get value通过_dirty变量来控制getter的触发的。

computed如何做缓存

当我们多次调用computedRef.value时,他会将computed reactiveEffect对象也加入到computedRef对象的deps中,在触发依赖时,如果不特殊处理,就会造成死循环,不会做缓存。

所以我们需要在触发依赖的时候先触发具有computed属性的依赖,再触发普通响应式依赖。

总结

  • 创建computedRefEmpl实例,内部通过_dirty变量判断是否触发依赖。触发依赖放在ReactiveEffect的调度器中执行,这样就可以区分普通的响应式数据和computed响应式数据执行了。并且先去触发computed的依赖函数,再去触发普通响应数据的依赖函数。(这样是为了做到computed缓存的)
  • 获取computed变量时,触发get value执行,然后收集依赖。并执行传入的依赖getter。并修改_dirty为false,如果依赖数据未变化,那么它将返回缓存的值。

只要修改响应式数据,就会触发调度器执行,然后_dirty设置为false,然后就会再次重新执行getter,拿到最新值。

watch

测试用例

js 复制代码
const { watch, reactive, effect } = Vue;

const obj = reactive({
  name: "zh"
})


watch(obj, (val) => {
  console.log("val", val)
})

setTimeout(() => {
  obj.name = "llm"
}, 2000)
  • 调用watch,创建ReactiveEffect对象。
  • watch依赖收集
  • 执行getter,拿到值,赋值给oldValue保存
  • 2s后触发reactive的setter方法,触发依赖,依赖是具有scheduler的,所以执行调度器,即job函数。
  • 触发watchCallback

这里需要注意一下,监听对象的变化,我们获取新旧值是一样的,经过上面的分析我们就可以看出,因为oldValue是执行ReactiveEffect中的fn返回的,它返回的是一个对象类型。新值也是这个对象,所以setter修改时,引用不变,所以新旧值是一样的。

断点调试

  • 调用doWatch函数执行初始化watch
  • 进行判断,如果是reactive对象,那么就深度监听。
  • 对象,递归调用,触发依赖收集
  • 定义job函数
  • 初始化ReactiveEffect对象和调度器。

任何关于响应式的api内部都离不开ReactiveEffect类的初始化,他就是通过Proxy get拦截器收集ReactiveEffect对象作为依赖,在触发Proxy set拦截器时,查看是否有scheduler回调(computed 触发get value的回调,watch第二个参数),如果有就执行,没有就执行普通的响应式回调。

watch实现代码

调度器Scheduler

  • 控制代码执行顺序。
js 复制代码
const { reactive, effect } = Vue

const obj = reactive({
  age: 1
})

effect(() => {
  console.log("=======", obj.age)
}, {
  scheduler() {
    setTimeout(() => {
      console.log("=======", obj.age)
    })
  }
})

obj.age = 2
console.log("执行结束!")

由上图可知,执行结束先于=====,2输出。

  • 控制代码执行逻辑。
js 复制代码
const { reactive, effect, queuePostFlushCb } = Vue

const obj = reactive({
  age: 1
})

effect(() => {
  console.log("=======", obj.age)
}, {
  scheduler() {
    queuePostFlushCb(() => {
      console.log("=======", obj.age)
    })
  }
})

obj.age = 2
obj.age = 3
console.log("执行结束!")

由上图可知,跳过了obj.age = 2的setter逻辑触发。

原理就是通过微任务队列执行调度器中的任务。

经过前面的分析,我们发现,scheduler对于计算属性和watch是非常重要的。

xdm,一起学习vue核心思想吧,为了能突破现状,加油啊。天天对着表单表格看,人都傻掉了。

往期文章

专栏文章

最近也在学习nestjs,有一起小伙伴的@我哦。

相关推荐
sszmvb123422 分钟前
测试开发 | 电商业务性能测试: Jmeter 参数化功能实现注册登录的数据驱动
jmeter·面试·职场和发展
测试杂货铺28 分钟前
外包干了2年,快要废了。。
自动化测试·软件测试·python·功能测试·测试工具·面试·职场和发展
王佑辉28 分钟前
【redis】redis缓存和数据库保证一致性的方案
redis·面试
真忒修斯之船35 分钟前
大模型分布式训练并行技术(三)流水线并行
面试·llm·aigc
GIS程序媛—椰子1 小时前
【Vue 全家桶】7、Vue UI组件库(更新中)
前端·vue.js
DogEgg_0011 小时前
前端八股文(一)HTML 持续更新中。。。
前端·html
ZL不懂前端1 小时前
Content Security Policy (CSP)
前端·javascript·面试
木舟10091 小时前
ffmpeg重复回听音频流,时长叠加问题
前端
王大锤43911 小时前
golang通用后台管理系统07(后台与若依前端对接)
开发语言·前端·golang
我血条子呢2 小时前
[Vue]防止路由重复跳转
前端·javascript·vue.js