Example method chapter -renderMixin

instance/render.js

export function renderMixin (Vue) {
    Vue.prototype.$nextTick = function (fn) {}}Copy the code

vm.$nextTick

Vm. $nextTick is an alias for global vue. nextTick and is used in the same way.

Defer the callback until after the next DOM update cycle. Use the data immediately after you modify it, and then wait for DOM updates. It is the same as the global method vue.nexttick, except that the this callback is automatically bound to the instance calling it.

Example:

<template> <div id="example">{{message}}</div> </template> <script> var vm = new Vue({ el: '##example', data: { message: }}) vm.message = 'new message' console.log(vm.$el.innerhtml) // '123' vue. nextTick(function () { console.log(vm.$el.innerHTML) // 'new message' }) </script>Copy the code

VUE is executed asynchronously when updating the DOM.

Whenever it listens for data changes, Vue opens an event queue and buffers all data changes that occur in the same event loop. If the same Watcher is fired more than once, it will only be pushed into the event queue once. This removal of duplicate data while buffering is important to avoid unnecessary computation and DOM manipulation. Then, in the next event loop, “TICK,” Vue refreshes the event queue and performs the actual (de-duplicated) work.

JS running mechanism

JS execution is single threaded and based on event loops

The event loop is divided into the following steps:

  1. All synchronization tasks are executed on the main thread, forming an execution stack; (stack)
  2. There is also a task queue, in which an event is placed whenever an asynchronous task has a result
  3. Once all synchronization tasks in the execution stack are complete, the system reads the task queue to see what events are in it, so the wait ends and execution begins
  4. The main thread repeats the above three steps

Task queues store tasks, which are divided into two types: macro tasks and micro tasks

After each macro task is executed, it is necessary to empty [all] microtasks in the microtask queue corresponding to the macro task

for (macroTask of macroTaskQueue) {
    // 1. Process the current macro task
    handleMacroTask();

    // 2. Process all corresponding microtasks
    for (microTask ofmicroTaskQueue) { handleMicroTask(microTask); }}Copy the code
  • Macro task (macro task) havesetTimeout,MessageChannel,postMessage,setImmediate;
  • Micro tasks (micro task) haveMutationObseverPromise.then.

Internal source

NextTick is located in the source code SRC /core/util/next-tick.js

Ability to detect

Vue internally attempts to use native Promise.then, MutationObserver, and setImmediate for asynchronous queues, and setTimeout(fn, 0) instead if the execution environment does not support it

Macro tasks take longer than microtasks, so microtasks are preferred when supported by the browser.

export let isUsingMicroTask = false

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

let timerFunc
/* Vue internally attempts to use native Promise for asynchronous queues. Then, MutationObserver, and setImmediate, if not supported by the implementation environment, SetTimeout (fn, 0) is used to replace macro tasks, which take longer than microtasks. Therefore, microtasks are preferred when supported by browsers. If the browser does not support microtasks, use macro tasks. However, the efficiency of various macro tasks can vary, depending on the browser's support, using different macro tasks */
if (typeof Promise! = ='undefined' && isNative(Promise)) {
  /* Microtask */
  const p = Promise.resolve()
  timerFunc = () = > {
    p.then(flushCallbacks)
    if (isIOS) setTimeout(noop)
  }
  isUsingMicroTask = true
} else if(! isIE &&typeofMutationObserver ! = ='undefined' && (
  isNative(MutationObserver) ||
  // PhantomJS and iOS 7.x
  MutationObserver.toString() === '[object MutationObserverConstructor]'
)) {
  /* Microtask */
  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 (typeofsetImmediate ! = ='undefined' && isNative(setImmediate)) {
  /* Macro task */
  timerFunc = () = > {
    setImmediate(flushCallbacks)
  }
} else {
  /* Macro task */
  timerFunc = () = > {
    setTimeout(flushCallbacks, 0)}}Copy the code

Execute the callback queue

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
    })
  }
}
Copy the code

This provides a promise-like call when nextTick does not pass cb arguments, such as:

this.$nextTick().then(() = > {
    consloe.log(1);
});
Copy the code
  1. How do you ensure that asynchronous methods are executed only when the first callback function is received?

    The nextTick source code uses the concept of an asynchronous lock, in which the lock is closed and the asynchronous method is executed on receiving the first callback function. At this point, the browser is waiting for the synchronous code to finish executing before executing the asynchronous code.

  2. Why do I need to back up the callback queue when I run flushCallbacks? Queue of callback functions that are also backed up?

There is a situation where nextTick is also used in the callback function of nextTick. If the flushCallbacks loop through the callback without special processing, the nextTick callback in the flushCallbacks will enter the callback queue.