在複雜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的事件循環區別?