Interviewer: What about $nextTick in Vue?

Company: Good future


What is NextTick

The official definition of it

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

That is, you can immediately get the dom updated data for updating

For example, the

Html structure

<div id="app"> {{ message }} </div>
Copy the code

Build a VUE instance

const vm = new Vue({
  el: '#app'.data: {
    message: Original value}})Copy the code

Modify the message

this.message = 'Modified value 1'
this.message = 'Modified value 2'
this.message = 'Modified value 3'
Copy the code

When you try to get the latest DOM node of the page, you get the old value instead

console.log(vm.$el.textContent) / / the original value
Copy the code

This is because vUE does not immediately update the Dom when a change is detected in the Message data. Instead, it places the change in an asynchronous operation queue

If we keep modifying the same data, the asynchronous operation queue will also be de-duplicated

After all data changes in the same event loop are complete, events in the queue are processed for DOM updates

Why nexttick

For example

{{num}}
for(let i=0; i<100000; i++){
    num = i
}
Copy the code

Without the nextTick update mechanism, the view would be updated every time num was updated (the above code would update the view 100,000 times). With the nextTick mechanism, you only need to update the view once, so nextTick is essentially an optimization strategy

Two, use scenarios

You can use vue.nexttick () if you want to get the updated DOM structure immediately after modifying the data.

The first argument is: callback function (to get the most recent DOM structure)

The second argument is: execute function context

$el.textContent) // The original value vue.nexttick (function () {// The DOM is updated Console. log(vm.$el.textContent) // Modified value})Copy the code

To use the vm.nexttick () instance method within a component, you only need to use the this.nexttick () instance method

Just go through this.nexttick () instance method just go through this.nexttick (), and this in the callback will automatically bind to the current Vue instance

this.message = 'Modified value'
console.log(this.$el.textContent) // => 'original value'
this.$nextTick(function () {
    console.log(this.$el.textContent) // => 'Modified value'
})
Copy the code

$nextTick() returns a Promise object that can do the same thing with async/await

this.message = 'Modified value'
console.log(this.$el.textContent) // => 'original value'
await this.$nextTick()
console.log(this.$el.textContent) // => 'Modified value'
Copy the code

Three, the implementation principle

/ SRC /core/util/next-tick.js

Callbacks are asynchronous operation queues

Pending indicates that the timerFunc function can only be executed once at a time

export function nextTick(cb? :Function, ctx? :Object) {
  let _resolve;

  // Cb callbacks are pushed into the callbacks array with uniform processing
  callbacks.push(() = > {
    if (cb) {
      // add try-catch to cb callback
      try {
        cb.call(ctx);
      } catch (e) {
        handleError(e, ctx, 'nextTick'); }}else if(_resolve) { _resolve(ctx); }});// Execute the asynchronous delay function timerFunc
  if(! pending) { pending =true;
    timerFunc();
  }

  // Return a promise-like call when nextTick passes no function arguments
  if(! cb &&typeof Promise! = ='undefined') {
    return new Promise(resolve= >{ _resolve = resolve; }); }}Copy the code

The timerFunc function defines which method to call based on which method is supported by the current environment.

Promise. Then, MutationObserver, setImmediate, setTimeout

Use either of the above methods to perform the degrade operation

export let isUsingMicroTask = false
if (typeof Promise! = ='undefined' && isNative(Promise)) {
  // Judgment 1: Whether to support Promises natively
  const p = Promise.resolve()
  timerFunc = () = > {
    p.then(flushCallbacks)
    if (isIOS) setTimeout(noop)
  }
  isUsingMicroTask = true
} else if(! isIE &&typeofMutationObserver ! = ='undefined' && (
  isNative(MutationObserver) ||
  MutationObserver.toString() === '[object MutationObserverConstructor]'
)) {
  // Judgment 2: Whether MutationObserver is supported natively
  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)) {
  // Judgment 3: Whether setImmediate is native supported
  timerFunc = () = > {
    setImmediate(flushCallbacks)
  }
} else {
  // Judgment 4: None of the above, use setTimeout directly
  timerFunc = () = > {
    setTimeout(flushCallbacks, 0)}}Copy the code

Both micro and macro tasks are used in flushCallbacks

Here we make a copy of the function inside the Callbacks and leave the callbacks empty

Execute the functions inside the Callbacks in turn

function flushCallbacks () {
  pending = false
  const copies = callbacks.slice(0)
  callbacks.length = 0
  for (let i = 0; i < copies.length; i++) {
    copies[i]()
  }
}
Copy the code

Summary:

Putting the executing function into the microtask or macro task loops events to the microtask or macro task, and the executing function executes the callbacks in turn