如何避免多次patch,提高性能?
在复杂Vue项目中,可能会同时修改多个响应式属性,每次属性修改都会触发watch.update
函数进行重新渲染,性能问题非常严峻,如何提高Vue的性能呢?
我们的编码目标是下面的demo能够成功渲染,并且最终字体颜色为yellow
,renderCout
的值为2。
let renderCount = 0;
let v = new Vue({
el: '#app',
data () {
return {
color: "red"
}
},
render (h) {
console.log('render:', ++renderCount)
return h('h1', {style: {color: this.color}}, 'hello world!')
}
})
setTimeout(() => {
v.color = 'black'
v.color = 'yellow'
}, 2000)
JS事件循环
JavaScript是单线程的,为避免单线程阻塞,JS设有异步事件队列。事件循环主要有2个步骤:
-
添加消息:异步事件会被推入事件队列等待执行,如
setTimeout(fn, 1000)
,1秒后fn函数被推入事件队列。 -
执行消息:当主线程执行完所有同步任务后,接着取出所有微任务执行,再取出宏任务执行,反复循环执行。
异步渲染
回顾上面的demo,我们同步修改颜色属性,因此是否可以将watch.update
方法设置为异步事件,等待所有属性修改完后再执行渲染函数?
v.color = 'black'
v.color = 'yellow'
首先我们修改update
方法,执行update
方法时调用queueWatcher
将实例推入队列中:
class Watch {
update() {
queueWatcher(this)
}
run() {
this.getAndInvoke(this.cb)
}
private getAndInvoke(cb: Function) {
let vm: Vue = this.vm
// let value = this.getter.call(vm, vm)
let value = this.get()
if (value !== this.value) {
if (this.options!.user) {
cb.call(vm, value, this.value)
} else {
cb.call(this.vm)
}
this.value = value
}
}
}
在模块内声明queue
队列,用于存储待更新的watch
实例;声明hasAddQueue
对象保证不重复添加实例。最后调用并调用nextTick
方法(等价于fn => setTimeout(fn, 0)
)。
let queue: ArrayWatch = []
let hasAddQueue: any = {}
function queueWatcher(watch: Watch): void {
if (!isTruth(hasAddQueue[watch.id])) {
hasAddQueue[watch.id] = true
if (!flush) {
queue.push(watch)
} else {
queue.push(watch)
}
if (!wait) {
wait = true
nextTick(flushQueue)
}
}
}
当JS执行完同步任务后,取出flushQueue
开始执行。函数从queue
队列中取出watch实例,并调用run
方法开始渲染。
function flushQueue(): void {
flush = true
try {
for (let i = 0; i < queue.length; ++i) {
let w: Watch = queue[i]
hasAddQueue[w.id] = null
w.run()
}
} catch (e) {
console.log(e)
}
}
Vue执行异步渲染的逻辑
Vue实例化后,将data.color
设为响应式的。
当执行v.color = 'black'
时,触发执行dep.notify
-> watch.update
-> queueWatcher
当执行v.color = 'yellow'
时,触发执行dep.notify
-> watch.update
-> queueWatcher
在执行queueWatcher
函数时,借助全局变量hasAddQueue
保证了同一个watch实例不会被重复添加
当所有同步任务执行完后,JS取出异步事件flushQueue
开始执行,随后调用watch.run
完成渲染。
总结
通过异步事件更新渲染,减少render的次数,大大提高了性能。在实际项目中,Vue.$nextTick
也非常重要,如在nextTick
的回调中获取更新后的真实dom。
杠精一下
JS和Nodejs的事件循环区别?