usage

NextTick is an important API in Vue. Take a look at the official documentation.

vue.nextTick( [callback, context] )

  • parameter

    • {Function} [callback]
    • {Object} [context]
  • usage

    A deferred callback is performed after the next DOM update loop ends. Use this method immediately after modifying the data to get the updated DOM.

    // Modify the data
    vm.msg = 'Hello'
    // DOM has not been updated yet
    Vue.nextTick(function () {
      // DOM is updated
    })
    
    // Use as a Promise (new since 2.1.0, see hints below)
    Vue.nextTick()
      .then(function () {
        // DOM is updated
      })
    Copy the code

As you can see, nextTick is essentially a callback after a DOM update. When is the time to update the DOM?

DOM update timing

This should start from the running mechanism of JS. (hard wide: JS operation mechanism). To put it simply, the sequence of js code execution is based on the event loop, which is roughly divided into several steps:

  • 1. All synchronization tasks are executed on the main thread, forming an execution stack.
  • 2. Asynchronous tasks are placed in a queue outside the main thread.
  • 3. After the simultaneous task execution is completed, the main program will look for asynchronous tasks in the task queue and put them into the execution stack.
  • 4. The main thread repeats the above three steps, which is also known as the event loop. Of course asynchronous tasks are divided into macro tasks and micro tasks. Specific did not say, can see next hard wide. After the synchronization task is executed, the main thread will first search and execute the microtask from the task queue until the last microtask is executed and the first event cycle ends. The second event loop begins, and the first macro task in the task queue becomes a synchronous task, which is executed first, then a microtask, and so on until all tasks are completed. The DOM update time is after the completion of the microtask. Let’s take a look:

      
<html lang="en">

<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="Width = device - width, initial - scale = 1.0">
  <meta http-equiv="X-UA-Compatible" content="ie=edge">
  <title>Document</title>
  <style>
    .box {
      width: 400px;
      height: 200px;
      margin: 0 auto;
      display: flex;
      justify-content: center;
      align-items: center;
      border: 1px solid # 259;
    }
  </style>
</head>

<body>
  <div class="box">
    <input class="input" type="text">
  </div>
</body>

<script>
  const box = document.querySelector('.box')
  function sleep(time) {
    const start = new Date().getTime();
    while (new Date().getTime() - start < time) { }
  }

  // click listens for events
  function onClick() {
    const testElement = document.querySelector('.input') | |' '
    testElement.focus()
    Promise.resolve().then(function () {
      console.log('promise')
      sleep(2000)
    })
    setTimeout((a)= > {
      sleep(2000)
      console.log('timeout')
    })
  }

  box.addEventListener('click', onClick)
</script>

</html>
Copy the code

NextTick source code analysis

/* @flow */
/* globals MutationObserver */

/ / empty function
import { noop } from 'shared/util'
// A function that handles errors
import { handleError } from './error'
// Determine the operating environment
import { isIE, isIOS, isNative } from './env'
// Whether to use the microtask identifier
export let isUsingMicroTask = false
// An array of callback functions
const callbacks = []
Nexttick Execution status
let pending = false

// Iterate over the callback function
function flushCallbacks() {
  pending = false
  // Shallow copy an array of callback functions
  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 microtasks.
// In 2.5 we use (macro) tasks In combination with microtasks.
// However, it has subtle problems when state is changed right before repaint
// (e.g. #6813, out-in transitions).
// Also, using (macro) tasks in event handler would cause some weird behaviors
// that cannot be circumvented (e.g. #7109, #7153, #7546, #7834, #8109).
// So we now use microtasks everywhere, again.
// A major drawback of this tradeoff is that there are some scenarios
// where microtasks have too high a priority and fire in between supposedly
// sequential events (e.g. #4521, #6690, which have workarounds)
// or even between bubbling of the same event (#6566).
let timerFunc

// The nextTick behavior leverages the microtask queue, which can be accessed
// via either native Promise.then or MutationObserver.
// MutationObserver has wider support, however it is seriously bugged in
// UIWebView in iOS >= 9.3.3 when triggered in touch event handlers. It
// completely stops working after triggering a few times... so, if native
// Promise is available, we will use it:
/* istanbul ignore next, $flow-disable-line */
if (typeof Promise! = ='undefined' && isNative(Promise)) {
  // If the browser supports native Promises, use promises directly
  const p = Promise.resolve()
  timerFunc = (a)= > {
    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)
  }
  // Change whether to use microtasks to true
  isUsingMicroTask = true
} else if(! isIE &&typeofMutationObserver ! = ='undefined' && (
  isNative(MutationObserver) ||
  // PhantomJS and iOS 7.x
  MutationObserver.toString() === '[object MutationObserverConstructor]'
)) {
  // Use MutationObserver where native Promise is not available,
  PhantomJS, iOS7, Android 4.4
  // (#6466 MutationObserver is unreliable in IE11)
  // MutationObserver: Use MutationObserver if the browser supports it
  // (This API is a microtask that can listen for changes in DOM elements and trigger events when all DOM operations are complete)
  // This API is preferred when promise is not supported
  let counter = 1
  const observer = new MutationObserver(flushCallbacks)
  const textNode = document.createTextNode(String(counter))
  observer.observe(textNode, {
    characterData: true
  })
  timerFunc = (a)= > {
    counter = (counter + 1) % 2
    textNode.data = String(counter)
  }
  isUsingMicroTask = true
} else if (typeofsetImmediate ! = ='undefined' && isNative(setImmediate)) {
  // Fallback to setImmediate.
  // Techinically it leverages the (macro) task queue,
  // but it is still a better choice than setTimeout.
  SetImmediate: A macro task that only supports Internet Explorer and Edge.
  // The advantage over setTimeout is that it executes immediately, whereas setTimeout has a minimum interval of 4ms
  timerFunc = (a)= > {
    setImmediate(flushCallbacks)
  }
} else {
  // Fallback to setTimeout.
  timerFunc = (a)= > {
    setTimeout(flushCallbacks, 0)}}// Encapsulates the nextTick function, cb: callback, CTX: this pointer
export function nextTick(cb? : Function, ctx? : Object) {
  let _resolve
  // The callback function goes into the callbacks for processing
  callbacks.push((a)= > {
    if (cb) {
      try {
        cb.call(ctx)
      } catch (e) {
        handleError(e, ctx, 'nextTick')}}else if (_resolve) {
      _resolve(ctx)
    }
  })
  if(! pending) { pending =true
    timerFunc()
  }
  // If no callback function is passed in and the current environment supports PROMISE, a Promise object is returned
  // $flow-disable-line
  if(! cb &&typeof Promise! = ='undefined') {
    return new Promise(resolve= > {
      _resolve = resolve
    })
  }
}

Copy the code