【vue源码系列6】组件如何实现高性能更新?(面试知识点)

58 阅读3分钟

vue面试经常会被问,vue3做了哪些性能优化,为什么这么快?

答案有很多,比如模板静态标记,事件缓存,diff算法,静态提升等。

除了这几个之外,其实还有一个知识点,就是多个数据更新,组件只执行一次更新渲染

具体是如何实现的呢?

我们一起去源码中找答案。

文章还是一样,分为3个部分,第一部分先了解一下前置知识点,第二部分看vue3源码实现,第三部分,自己实现一个简单的demo加深理解。

前置知识点

1.effect的使用

在之前的文章,我们通过schedule以及_dirty变量来控制computed是否重新计算。

也就是说,schedule其实是可以控制的。

了解到这就可以了,详细的前面大概有3篇文章都在讲effect的使用,这里就不赘述了。

感兴趣的可以看一下vue源码系列的4,5两篇文章。

2.微任务与宏任务

这个如果不清楚的,可以搜一下,网上介绍的文章挺多的。

简单来说,js代码执行分为微任务与宏任务,在同一个调用栈中,先执行宏任务,后执行微任务。常见的宏任务为script,setTimeout,setInterval,常见的微任务为promise。

举个栗子:


   for(let i=0; i< 3; i++) {
        console.log('i',i)
    }
    console.log('for end')

打印输出1,2,3,循环结束后,输出for end。

image.png

但是如果我们想让for end 先输出,再打印1,2,3要怎么做呢?

    const pro = Promise.resolve()
    for(let i=0; i< 3; i++) {
        pro.then(() => {
            console.log('i',i)
        })
    }
    console.log('for end')

我们可以先创建一个微任务。

因为pro是微任务,所以会先执行for end,

image.png

源码阅读

在runtime/core/renderer.ts里面有一个监听组件更新的函数setupRenderEffect。

image.png

我们先不看他具体函数做了什么,只看逻辑。

1.声明了一个组件更新函数

2.创建一个effect侦听器

3.将更新函数绑定到组件实例

4.执行更新函数

effect侦听器我们前面介绍过,每次数据变换的时候,会重新执行scheduler。

也就是说,每次数据变化,都会执行queueJob(update)。

我们看看这个函数做了什么。

image.png

1.dep数组,push一下update

2.执行queueFlush

const pendingPostFlushCbs: SchedulerJob[] = []

image.png

是不是很熟悉,其实就是我们前面介绍的微任务。

那flushJobs又做了什么呢?

image.png

其实就是遍历queue,并执行函数。

ps(callWithErrorHandling函数其实就是加了异常处理,相当于直接执行job)

手写源码


      const { ReactiveEffect, ref } = Vue

      const firstName = ref('')
      const lastName = ref('')

      let queue = []
      let isFlushPending = false
      let resolvePromise = Promise.resolve()

      const componentUpdateFn = () => {
        console.log('渲染组件', firstName.value + lastName.value)
      }

      const flushJobs = () => {
        isFlushPending = false
        let activePreFlushCbs = [...new Set(queue)]
        queue.length = 0
        activePreFlushCbs.forEach(item => {
          item()
        })
      }

      const queueJob = fn => {
        queue.push(fn)
        if (!isFlushPending) {
          isFlushPending = true
          resolvePromise.then(flushJobs)
        }
      }

      const effect = new ReactiveEffect(componentUpdateFn, () =>
        queueJob(update)
      )

      const update = () => effect.run()

      update()

      setTimeout(() => {
        firstName.value = 'zhang'
        lastName.value = 'shan'

        console.log('queue', queue)
      }, 2000)


手写部分主要是梳理核心逻辑,写法略微会有出入。

总结一下

vue组件更新的核心逻辑在于。

1. 当响应式数据发生改变,会触发组件监听函数。

2. 为了确保组件更新的性能,会将函数添加到微任务队列中,等待宏任务执行完毕之后再执行。

3. 执行微任务的时候,再做逻辑判断,是否更新组件,比如数组去重。

至于组件更新的具体逻辑,后面再介绍。