The JSVAScript event loop mechanism is the core principle of nextTick, and it is strongly recommended to read the following article carefully to see the code below.

Vue3’s nextTick implementation will be updated later in this article

Understand the loop mechanism between EventLoop and stack and host environment

VUE 2.x ntxtTick implementation

In the browser environment

Common macrotasks are

  • MessageChannel
  • setTimeout
  • postMessage
  • setImmediate

Common microtasks are

  • MutationObsever
  • Promise.then

A separate nextTick implementation file SRC /core/util/next-tick.js is maintained for Vue2.5 +. The SRC directory holds the Vue source code implementation. Here is the directory organization of the core code in the SRC directory.

  • compiler: compiler code that generates the render function
  • core: Core code, the platform-independent core
  • platforms: platform support, platform specific code, related entry files
  • serve: Server rendering
  • sfc: Single file system implementation
  • shard: Shared code, common code throughout the code base

Let’s seenextTickThe source of

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

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]()
  }
}

// Here we have async deferring wrappers using both microtasks and (macro) tasks.
// In < 2.4 we use microtasks everywhere, but there are some scenarios where
// microtasks have too high a priority and fire in between supposedly
// sequential events (e.g. #4521, #6690) or even between bubbling of the same
// event (#6566). However, using (macro) tasks everywhere also has subtle problems
// when state is changed right before repaint (e.g. #6813, out-in transitions).
// Here we use microtask by default, but expose a way to force (macro) task when
// needed (e.g. in event handlers attached by v-on).
let microTimerFunc
let macroTimerFunc
let useMacroTask = false

// Determine (macro) task defer implementation.
// Technically setImmediate should be the ideal choice, but it's only available
// in IE. The only polyfill that consistently queues the callback after all DOM
// events triggered in the same loop is by using MessageChannel.
/* istanbul ignore if */
if (typeofsetImmediate ! = ='undefined' && isNative(setImmediate)) {
  macroTimerFunc = () = > {
    setImmediate(flushCallbacks)
  }
} else if (typeofMessageChannel ! = ='undefined' && (
  isNative(MessageChannel) ||
  // PhantomJS
  MessageChannel.toString() === '[object MessageChannelConstructor]'
)) {
  const channel = new MessageChannel()
  const port = channel.port2
  channel.port1.onmessage = flushCallbacks
  macroTimerFunc = () = > {
    port.postMessage(1)}}else {
  /* istanbul ignore next */
  macroTimerFunc = () = > {
    setTimeout(flushCallbacks, 0)}}// Determine microtask defer implementation.
/* istanbul ignore next, $flow-disable-line */
if (typeof Promise! = ='undefined' && isNative(Promise)) {
  const p = Promise.resolve()
  microTimerFunc = () = > {
    p.then(flushCallbacks)
    // in problematic UIWebViews, Promise.then doesn't completely break, but
    // it can get stuck in a weird state where callbacks are pushed into the
    // microtask queue but the queue isn't being flushed, until the browser
    // needs to do some other work, e.g. handle a timer. Therefore we can
    // "force" the microtask queue to be flushed by adding an empty timer.
    if (isIOS) setTimeout(noop)
  }
} else {
  // fallback to macro
  microTimerFunc = macroTimerFunc
}

/** * Wrap a function so that if any code inside triggers state change, * the changes are queued using a (macro) task instead of a microtask. */
export function withMacroTask (fn: Function) :Function {
  return fn._withTask || (fn._withTask = function () {
    useMacroTask = true
    const res = fn.apply(null.arguments)
    useMacroTask = false
    return res
  })
}

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
    if (useMacroTask) {
      macroTimerFunc()
    } else {
      microTimerFunc()
    }
  }
  // $flow-disable-line
  if(! cb &&typeof Promise! = ='undefined') {
    return new Promise(resolve= > {
      _resolve = resolve
    })
  }
}
Copy the code

Here, Vue also declares

  • microTimerFunc(Micro-task realization)
  • macroTimerFunc(Macro task implementation)
  • useMacroTask(Whether to use the macro task flag bit)

From the top down, we first look at the code logic that implements the macro task

  • First determine if there is onesetImmediateYou can use
  • And determine if there is oneMessageChannelYou can use
  • Otherwise downgrade to usesetTimeout

What you can see here is the priority of how the macro tasks are implemented, and how Vue determines whether the methods implemented in those ways are native implementations. Avoid inconsistent behavior from third party profill.

Next comes the microTimerFunc microtask implementation. As you can see, Vue determines if there is a native Promise implementation, if there is a Promise. Resolve, if not, points to the implementation of the macro task.

Here Vue exports two functions

  • nextTick
  • withMacroTask

nextTick

In this case, the method of closure is used to cache the callback functions passed in by nextTick for multiple execution in the same tick, and press these callback functions into the same synchronization task for one execution in the nextTick, ensuring the accuracy of multiple execution. At the same time, according to the status, identification bits use the corresponding implementation mode.

withMacroTask

The exported withMacroTask is actually a decorator function that wraps the passed function into a new function, returns the result of the original function execution, sets useMacroTask to true at function execution time, so that the nextTick at execution time forces the macro task implementation to be removed.

The Decorator Pattern allows you to add new functionality to an existing object without changing its structure. This type of design pattern is a structural pattern that acts as a wrapper around existing classes.

This pattern creates a decorator class that wraps the original class and provides additional functionality while preserving the integrity of the class method signature.