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。
但是如果我们想让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,
源码阅读
在runtime/core/renderer.ts里面有一个监听组件更新的函数setupRenderEffect。
我们先不看他具体函数做了什么,只看逻辑。
1.声明了一个组件更新函数
2.创建一个effect侦听器
3.将更新函数绑定到组件实例
4.执行更新函数
effect侦听器我们前面介绍过,每次数据变换的时候,会重新执行scheduler。
也就是说,每次数据变化,都会执行queueJob(update)。
我们看看这个函数做了什么。
1.dep数组,push一下update
2.执行queueFlush
const pendingPostFlushCbs: SchedulerJob[] = []
是不是很熟悉,其实就是我们前面介绍的微任务。
那flushJobs又做了什么呢?
其实就是遍历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. 执行微任务的时候,再做逻辑判断,是否更新组件,比如数组去重。
至于组件更新的具体逻辑,后面再介绍。