Vue源码窥探之 nextTick 机制

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/qq_35534823/article/details/88394044

MicroTask(微任务) 和 MacroTask(宏任务)

在说 nextTick 之前,需要对 microTaskmacroTaskEvent Loop 有一定了解。详见JavaScript的运行机制Event Loop(事件循环)microTask 是把任务放在主线程的末尾,而 macroTask 是把任务放在 Task Queue 中,所以当我们执行完主线程的代码后,会先执行 microTask,再执行 macroTask
常见的 microTask 有:process.nextTick(Node.js特有的)PromiseMutationObserver 等;
常见的 macroTask 有: setImmediateMessageChannelsetTimeoutsetInterval 等。

nextTick 的实现

Vue 中,nextTick 是一个被重写次数较多的一个 API。
2.4 之前的版本中, nextTick 的实现是一律采用 microTask
microTask 采用的优先级顺序为 1,Promise ; 2,MutationObserver ;3, setTimeout

但是 microTask 的优先级太高,会比事件冒泡还要快(#6566),以及一些顺序事件(#4521 #6690)的bug。

2.5 的版本中,nextTick 的实现改为 microTask + macroTask 的结合。默认采用 microTask,在一些事件绑定的地方会强制使用 macroTask
microTask 中采用的优先级顺序为:1,Promise ; 2,macroTask
macroTask 中采用的优先级顺序为:1,setImmediate ; 2,MessageChannel;3,setTimeout

但这也会出现一些问题,重绘问题(#6813)及一些不可修复的奇怪问题(#7109#7153#7546#7834#8109)。

所以在 2.6 的版本中,nextTick 重新改为 microTask 的实现,然后单独把 2.4 中出现的bug修复掉。但需要注意的是 2.6 中相对于 2.4 多了一个 setImmediate
采用的优先级顺序为: 1,Promise ; 2,MutationObserver ;3, setImmediate ;4,setTimeout
其中前2个为 microTask ,后两个为 macroTask

MutationObserver 是可以监听到指定DOM的变化;
setImmediate 是在客户端实现的 process.nextTick,现阶段只有IE实现了。

这里是相关代码

import { noop } from 'shared/util'
import { handleError } from './error'
import { isIE, isIOS, isNative } from './env'

export let isUsingMicroTask = false

let timerFunc

if (typeof Promise !== 'undefined' && isNative(Promise)) {
  const p = Promise.resolve()
  timerFunc = () => {
    p.then(flushCallbacks)
  }
  isUsingMicroTask = true
} else if (!isIE && typeof MutationObserver !== 'undefined' && (
  isNative(MutationObserver) ||
  // PhantomJS and iOS 7.x
  MutationObserver.toString() === '[object MutationObserverConstructor]'
)) {
  let counter = 1
  const observer = new MutationObserver(flushCallbacks)
  const textNode = document.createTextNode(String(counter))
  observer.observe(textNode, {
    characterData: true
  })
  timerFunc = () => {
    counter = (counter + 1) % 2
    textNode.data = String(counter)
  }
  isUsingMicroTask = true
} else if (typeof setImmediate !== 'undefined' && isNative(setImmediate)) {
  timerFunc = () => {
    setImmediate(flushCallbacks)
  }
} else {
  timerFunc = () => {
    setTimeout(flushCallbacks, 0)
  }
}

接下来是 nextTick 的执行逻辑:

  1. 把 cb 函数先放到 callbacks 这个数组中,这样可以保证多个 cb 可以在同一个 tick 中被调用。
  2. 最终按照优先顺序去执行 flushCallbacks
  3. 循环执行 callbacks 中的 cb。
const callbacks = []
let pending = false

function flushCallbacks () {
  pending = false
  const copies = callbacks.slice(0)
  callbacks.length = 0
  for (let i = 0; i < copies.length; i++) {
    copies[i]()
  }
}
export function nextTick (cb?: Function, ctx?: Object) {
  let _resolve
  // 这里是保证了当前事件循环中的所有的 nextTick 的 cb ,都会在 timerFunc 中统一被调用。
  callbacks.push(() => {
    if (cb) {
      try {
        cb.call(ctx)
      } catch (e) {
        handleError(e, ctx, 'nextTick')
      }
    } else if (_resolve) {
      _resolve(ctx)
    }
  })
  if (!pending) {
    pending = true
    timerFunc()
  }
}

猜你喜欢

转载自blog.csdn.net/qq_35534823/article/details/88394044
今日推荐