深度理解Vue中nextTick

2020-10-05 13:00:19

概念:

nextTick:
nextTick主要是使用了宏任務 (macrotask) 和微任務 (microtask) ,定義了一個非同步方法,多次呼叫 nextTick 會將方法存入callback佇列中,通過這個非同步方法清空當前佇列

macrotask:
setTimeout, setInterval, setImmediate, I/O, UI rendering

microtask:
process.nextTick, Promise, MutationObserver

順序:

任務佇列中,在每一次事件迴圈中,macrotask只會提取一個執行,而microtask會一直提取,直到microsoft佇列為空為止,主執行緒執行完成該任務後又會檢查microtasks佇列並完成裡面的所有任務後再執行macrotask

場景:

  1. 在資料變化後要執行的某個操作,並隨著操作需要改變dom更新的時候
  2. 在 created 或者 mounted 階段,需要操作後更新dom時
  3. 如果先執行了nextTick再更新資料會無效

nextTick和$nextTick:

nextTick(callback): 全域性方法,當資料發生變化,更新後執行回撥
$nextTick(callback): 實體方法,自動把context引數繫結為呼叫它的範例,當dom發生變化,更新後執行的回撥,一般會使用this. $nextTick

原理:

  1. 呼叫nextTick並傳入兩個引數:回撥函數cb和回撥函數的執行上下文ctx
  2. 判斷是否有回撥函數
  3. 有就存入佇列
  4. 沒有就返回promise
  5. 判斷是否在執行回撥函數
  6. 如果沒有則執行timeFunc非同步方法,多次執行nextTick只會執行一次timerFunc
  7. timeFunc中選擇一個非同步方法:
    (1)先嚐試promise回撥,進行非同步執行flushCallbacks
    (2)若不支援則繼續嘗試MutationObserver回撥,會建立一個文位元組點進行監聽
    (3)若不支援則繼續嘗試setImmediate回撥,在setImmediate下執行flushCallbacks
    (4)都不支援則使用setTimeout(flushCallbacks, 0)
  8. 執行flushCallbacks方法

原始碼:

nextTick

export function nextTick (cb?: Function, ctx?: Object) {
  let _resolve
  // 將拿到的回撥函數存放到陣列中
  callbacks.push(() => { 
    if (cb) {
      try { // 錯誤捕獲
        cb.call(ctx)
      } catch (e) {
        handleError(e, ctx, 'nextTick')
      }
    } else if (_resolve) {
      _resolve(ctx)
    }
  })
  // 如果當前沒有在執行,就會執行timeFunc
  if (!pending) { 
  	//標記正在執行
    pending = true 
    // 多次執行nextTick只會執行一次,timerFunc就是一個非同步方法
    timerFunc() 
  }
}

timeFunc

// 判斷是否原生支援promise
if (typeof Promise !== 'undefined' && isNative(Promise)) {
  const p = Promise.resolve()
  // flushCallbacks就包裹了一個promise
  timerFunc = () => { 
  	// 如果支援則非同步的去執行flushCallbacks
    p.then(flushCallbacks) 
    if (isIOS) setTimeout(noop)
  }
  // 標記微任務
  isUsingMicroTask = true 
  // 判斷是否原生支援MutationObserver
} else if (!isIE && typeof MutationObserver !== 'undefined' && (
  isNative(MutationObserver) ||
  MutationObserver.toString() === '[object MutationObserverConstructor]'
)) {
  // 也是一個微任務
  let counter = 1
  // new了一個MutationObserver類
  const observer = new MutationObserver(flushCallbacks) 
  // 建立了一個文位元組點
  const textNode = document.createTextNode(String(counter))
  // 原生api,幫我們監聽一個節點
  // 當資料發生變化了就會非同步執行flushCallbacks方法
  observer.observe(textNode, { 
    characterData: true 
  })
  timerFunc = () => {
    counter = (counter + 1) % 2
    // 資料更新
    textNode.data = String(counter)
  }
  // 標記微任務
  isUsingMicroTask = true
  // 判斷是否原生支援setImmediate
} else if (typeof setImmediate !== 'undefined' && isNative(setImmediate)) { 
  // setImmediate原生方法,預設ie下有,高版本的谷歌也支援
  timerFunc = () => {
  	// 直接執行
    setImmediate(flushCallbacks)
  }
} else {
  // 如果以上都不支援則採用setTimeout
  timerFunc = () => {
    setTimeout(flushCallbacks, 0)
  }
}

flushCallbacks

// 回撥函數佇列
const callbacks = [] 
// 空閒狀態準備執行
let pending = false 
 // 多個nextTick中傳遞的回撥函數依次執行
function flushCallbacks () {
  pending = false
  // 拷貝一份禁止套娃
  const copies = callbacks.slice(0) 
  // 清空佇列
  callbacks.length = 0 
  // cb執行過程中可能又會往callbacks中加入內容
  // 遍歷完拷貝的佇列,新任務在下一輪存入callbacks執行
  for (let i = 0; i < copies.length; i++) {
    copies[i]()
  }
}