Vue中nextTick的原理

Vue中會(huì)用到一個(gè)nextTick()的方法,在Vue實(shí)例中更改數(shù)據(jù)之后需要改動(dòng)DOM,如果需要操作更新后的DOM,就要使用nextTick(),確保內(nèi)部的函數(shù)是在下一輪循環(huán)中才會(huì)執(zhí)行。

官網(wǎng)的定義如下:

在下次 DOM 更新循環(huán)結(jié)束之后執(zhí)行延遲回調(diào)。在修改數(shù)據(jù)之后立即使用這個(gè)方法,獲取更新后的 DOM。


Vue.nextTick

nextTick使用方法有回調(diào)和Promise兩種,更常見(jiàn)的是在實(shí)例調(diào)用this.$nextTick(() => {回調(diào)方法})。

淺談nextTick原理

在項(xiàng)目文件夾中,可以找到nextTick源碼,具體地址是node_modules\vue\src\core\util\next-tick.js,代碼不長(zhǎng),添加自己理解的備注后代碼如下:

import { noop } from 'shared/util'
// 錯(cuò)誤處理函數(shù)
import { handleError } from './error'
// 是否是IE、IOS、內(nèi)置函數(shù)
import { isIE, isIOS, isNative } from './env'
// 使用微任務(wù)標(biāo)識(shí)符
export let isUsingMicroTask = false
// 存回調(diào)函數(shù)的數(shù)組
const callbacks = []
// pending 是一個(gè)標(biāo)識(shí)符,
let pending = false
// 遍歷執(zhí)行回調(diào)函數(shù)
function flushCallbacks () {
  pending = false
  const copies = callbacks.slice(0)
  callbacks.length = 0
  for (let i = 0; i < copies.length; i++) {
    copies[i]()
  }
}
// 根據(jù)下面的判斷來(lái)給timerFunc定義
let timerFunc
// 首先第一順位是使用Promise實(shí)現(xiàn)延遲回調(diào),并且判斷是否是原生Promise
if (typeof Promise !== 'undefined' && isNative(Promise)) {
  const p = Promise.resolve()
  // 將timerFunc定義為Promise,并且延遲通過(guò)lushCallbacks執(zhí)行所有回調(diào)
  timerFunc = () => {
    p.then(flushCallbacks)
    if (isIOS) setTimeout(noop)
  }
  // 設(shè)置使用微任務(wù)標(biāo)識(shí)符 因?yàn)镻romise.then()是一個(gè)微任務(wù)
  isUsingMicroTask = true
} else if (!isIE && typeof MutationObserver !== 'undefined' && (
  isNative(MutationObserver) ||
  MutationObserver.toString() === '[object MutationObserverConstructor]'
))
 // 當(dāng)不是原生Promise時(shí),第二順位是使用MutationObserver API
 {
  // 
  let counter = 1
  // 創(chuàng)建MutationObserver,用于監(jiān)聽(tīng)DOM變化,并且執(zhí)行flushCallbacks函數(shù)
  const observer = new MutationObserver(flushCallbacks)
  // 設(shè)置一個(gè)觀測(cè)節(jié)點(diǎn)
  const textNode = document.createTextNode(String(counter))
  // 將觀測(cè)節(jié)點(diǎn)設(shè)置為觀察目標(biāo)的改變
  observer.observe(textNode, {
    characterData: true
  })
  // 定義timerFunc
  timerFunc = () => {
    // counter會(huì)在0/1之間切換 
    counter = (counter + 1) % 2
    // 并且將新counter寫(xiě)入到觀測(cè)節(jié)點(diǎn),這個(gè)數(shù)值的變化就會(huì)觸發(fā)MutationObserver的檢測(cè),從而觸發(fā)回調(diào)執(zhí)行
    textNode.data = String(counter)
  }
  isUsingMicroTask = true
} else if (typeof setImmediate !== 'undefined' && isNative(setImmediate))
 {
  // 當(dāng)Promise和MutationObserver,都無(wú)法使用時(shí),用setImmediate定時(shí)器,這里就算宏任務(wù)了 
  timerFunc = () => {
    setImmediate(flushCallbacks)
  }
} else {
  // 上面三種情況都不可時(shí)候用的時(shí)候,就用最普通的定時(shí)器,宏任務(wù)切換到下一次循環(huán) 
  timerFunc = () => {
    setTimeout(flushCallbacks, 0)
  }
}
// 實(shí)際暴露出去的函數(shù)
export function nextTick (cb?: Function, ctx?: Object) {
  let _resolve
  // 統(tǒng)一處理回調(diào)函數(shù)
  callbacks.push(() => {
    if (cb) {
      try {
        cb.call(ctx)
      } catch (e) {
        // 錯(cuò)誤處理函數(shù)
        handleError(e, ctx, 'nextTick')
      }
    } else if (_resolve) {
      _resolve(ctx)
    }
  })
  // pending 為false 說(shuō)明本輪事件循環(huán)中沒(méi)有執(zhí)行過(guò)timerFunc()
  if (!pending) {
    pending = true
    timerFunc()
  }
  // 如果沒(méi)傳入cb回調(diào)函數(shù),就自動(dòng)創(chuàng)建個(gè)Promise
  if (!cb && typeof Promise !== 'undefined') {
    return new Promise(resolve => {
      _resolve = resolve
    })
  }
}

代碼整體是好理解的,具體實(shí)現(xiàn)異步的方法順位是Promise > MutationObserver > setImmediate > setTimeout,就是使用這4種方法,包裹c(diǎn)allback函數(shù),使得callback可以異步執(zhí)行,其中MutationObserver是HTML5中添加,用于監(jiān)視DOM變動(dòng)的接口,它可以監(jiān)聽(tīng)一個(gè)DOM對(duì)象上發(fā)生的子節(jié)點(diǎn)刪除、屬性修改、文本內(nèi)容修改等。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

友情鏈接更多精彩內(nèi)容