Vue2的nextTick源码解析及原理

126 阅读1分钟
对于Vue2中的nextTick,大家也都不陌生,**nextTick是将注入的回调函数以微任务的形式放到本次事件循环队列上去,如果不支持就放到下一次时间队列中**。接下来我们看一下nextTick的源码解析:

创建flushCallbacks函数执行器

        const callbacks = []    // 用于存储$nextTick传入的函数
        let pending = false
        //flushCallbacks用于遍历执行回调函数
        function flushCallbacks () {
          pending = false
          const copies = callbacks.slice(0)
          callbacks.length = 0
          for (let i = 0; i < copies.length; i++) {
            copies[i]()
          }
        }

创建微任务包装器,做判断

        let timerFunc;//使用微任务的异步延迟包装器

1、先判断浏览器是否支持Promise,如果为true,则用Promise来实现

    if (typeof Promise !== 'undefined' && isNative(Promise)) {
      //Promise.resolve()等价于 new Promise(resolve => resolve())
      const p = Promise.resolve()
      timerFunc = () => {
          p.then(flushCallbacks)      //then函数注册回调
          if (isIOS) setTimeout(noop)
      }
      isUsingMicroTask = true
    }

2、在判断浏览器是否支持MutationObserver,如果支持,则使用MutationObserver来实现

    else if (!isIE && typeof MutationObserver !== 'undefined' && (
      isNative(MutationObserver) ||
      // PhantomJS and iOS 7.x
      MutationObserver.toString() === '[object MutationObserverConstructor]'
    )) {
      // Use MutationObserver where native Promise is not available,
      // e.g. PhantomJS, iOS7, Android 4.4
      // (#6466 MutationObserver is unreliable in IE11)
      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
    }
   MutationObserver API在MDN描述是:提供了监视对DOM树所做更改的能力,它会在指定的DOM发生变化时被调用。vue是创建一个文本节点,通过监听文本节点变化来执行MutationObserver的注册函数调用

3、如果浏览器对上述微任务API都不支持,则使用setImmediate/setTimeout来异步执行回调

    else if (typeof setImmediate !== 'undefined' && isNative(setImmediate)) {
      // Fallback to setImmediate.
      // Technically it leverages the (macro) task queue,
      // but it is still a better choice than setTimeout.
      timerFunc = () => {
        setImmediate(flushCallbacks)
      }
    } else {
      // Fallback to setTimeout.
      timerFunc = () => {
        setTimeout(flushCallbacks, 0)
      }
    }

4。判断出浏览器支持的API后,最后一步便是执行

    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)
        }
      })
      if (!pending) {
        pending = true
        timerFunc()
      }
      // $flow-disable-line
      if (!cb && typeof Promise !== 'undefined') {
        return new Promise(resolve => {
          _resolve = resolve
        })
      }
    }
导出nextTick方法,可接受两个参数cb是回调函数,ctx执行上下文首先判断cb是否存在,存在则将绑定了ctx的回调函数push入callbacks中pending默认值false,然后调用timerFunc微任务包装器来执行我们传入的回调

** 总结:在 nextTick 函数中把通过参数 cb 传入的函数,做一下包装然后 push 到 callbacks 数组中。然后用变量 pending 来保证执行一个事件循环中只执行一次 timerFunc()。最后执行 if (!cb && typeof Promise !== 'undefined'),判断参数 cb 不存在且浏览器支持 Promise,则返回一个 Promise 类实例化对象。例如 nextTick().then(() => {}),当 _resolve 函数执行,就会执行 then 的逻辑中。