在复杂Vue项目中,可能会同时修改多个响应式属性,每次属性修改都会触发watch.update
函数进行从新渲染,性能问题很是严峻,如何提升Vue的性能呢?bash
咱们的编码目标是下面的demo可以成功渲染,而且最终字体颜色为yellow
,renderCout
的值为2。app
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)
复制代码
JavaScript是单线程的,为避免单线程阻塞,JS设有异步事件队列。事件循环主要有2个步骤:dom
添加消息:异步事件会被推入事件队列等待执行,如setTimeout(fn, 1000)
,1秒后fn函数被推入事件队列。异步
执行消息:当主线程执行完全部同步任务后,接着取出全部微任务执行,再取出宏任务执行,反复循环执行。函数
回顾上面的demo,咱们同步修改颜色属性,所以是否能够将watch.update
方法设置为异步事件,等待全部属性修改完后再执行渲染函数?post
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
方法开始渲染。ui
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实例化后,将data.color
设为响应式的。this
当执行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的事件循环区别?