【Ts重構Vue】04-異步渲染

如何避免屢次patch,提升性能?

在複雜Vue項目中,可能會同時修改多個響應式屬性,每次屬性修改都會觸發watch.update函數進行從新渲染,性能問題很是嚴峻,如何提升Vue的性能呢?bash

咱們的編碼目標是下面的demo可以成功渲染,而且最終字體顏色爲yellowrenderCout的值爲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)
複製代碼

JS事件循環

JavaScript是單線程的,爲避免單線程阻塞,JS設有異步事件隊列。事件循環主要有2個步驟:dom

  1. 添加消息:異步事件會被推入事件隊列等待執行,如setTimeout(fn, 1000),1秒後fn函數被推入事件隊列。異步

  2. 執行消息:當主線程執行完全部同步任務後,接着取出全部微任務執行,再取出宏任務執行,反覆循環執行。函數

異步渲染

回顧上面的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執行異步渲染的邏輯

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

系列文章

【Ts重構Vue】00-Ts重構Vue前言

【Ts重構Vue】01-如何建立虛擬節點

【Ts重構Vue】02-數據如何驅動視圖變化

【Ts重構Vue】03-如何給真實DOM設置樣式

【Ts重構Vue】04-異步渲染

【Ts重構Vue】05-實現computed和watch功能

相關文章
相關標籤/搜索