This is the 13th day of my participation in Gwen Challenge.

preface

Previously, we have outlined the overall flow and rationale of vue.js. Next, we will explore the rationale and implementation of some methods on vue.js instances or components.

This time, we’ll explore nextTick.

vm.$nextTick

NextTick takes a callback function as an argument, and its purpose is to defer the callback until after the next DOM update cycle. It is the same as the global method vue.nexttick, except that the this callback is automatically bound to the instance calling it. If no callback is provided and in an environment that supports promises, a Promise is returned.

Application scenarios

We have a scenario in our development projects where we need to do something with the new DOM after updating the state (data), but we can’t actually get the updated DOM because we haven’t rerendered it yet. At this point we need to use the nextTick method.

new Vue({
    methods: {
        example: function() {
            this.message = 'changed';
            this.$nextTick(function(){
                // Dom is updated
                // this points to the current instance
                this.doSomethingElse()
            })
        }
    }
})
Copy the code

In vue.js, Watcher is notified when the state changes and triggers the rendering process of the virtual DOM. Watcher does not trigger the render operation synchronously, but asynchronously. Vue.js has a queue to which Watcher is pushed whenever rendering is needed, and watcher triggers the rendering process in the next event loop.

Why does vue.js use asynchronous update queues

As we learned earlier, vue.js 2.0 introduced the virtual DOM, and notification of change detection is only sent to components. So, if we change both states of a component at the same time, does the component have to be rerendered twice? This shows to be unreasonable.

How to solve the problem of duplicate rendering?

We simply add the Watcher instance to a queue and cache it, check to see if it has already been added to the queue, and only add it to the queue if it doesn’t already exist. In the next event loop, Watcher can be triggered in turn to rerender. This way, in an event loop, if multiple states of a component change, the component will only be re-rendered once.

Asynchronous tasks

To achieve this effect, we need to use asynchronous tasks. Asynchronous tasks in Javascript fall into two types: microtasks and macro tasks.

Microtask events include but are not limited to the following:

  • Promise.then()
  • MutationObserver
  • Object.observe
  • process.nextTick

Events belonging to macro tasks include but are not limited to the following:

  • setTimeout
  • setInterval
  • settImmediate
  • MessageChannel
  • requestAnimationFrame
  • I/O
  • UI interaction events

What is an execution stack

When we execute a method, JavaScript generates an execution context for that method. The execution environment has the method’s private scope, the pointer to the upper scope, the method’s parameters, the variables defined in the private scope, and the this object. The execution environment is added to a stack, which is the execution stack.

Going back to the previous question, “next DOM update cycle” actually means updating the DOM the next time a microtask is executed. The vm.$nextTick actually adds the callback to the microtask. Demoted to macro tasks only in exceptional cases, they are added to microtasks by default.

Note that either a callback to update the DOM or a callback registered with vm.$nextTick adds tasks to the microtask queue, so whichever task is added to the queue first is executed first.

NextTick’s internal registration process and execution process

Graph subgraph execution flow b[task executed] --> c[execute all callbacks in sequence] --> d[clear callbacks] end Subgraph registration flow n[nextTick] --> S [add callback function to callbacks] s --> t{first use of <br>nextTick? } -- no --> e[end] t -- yes --> A [add task to task queue] --> e end
const callbacks = [];
let pending = false; // To mark whether nextTick is used for the first time in this cycle

function flushCallbacks(){
    pending = false;
    const copies = callbacks.slice(0);
    callbacks.length = 0; / / empty callbacks
    // Execute the callbacks registered in callbacks in turn
    for(let i = 0; i < copies.length; i++){
        copies[i]()
    }
}

let microTimerFunc
const p = Promise.resolve()
// Prioritize microtasks for asynchrony
microTimerFunc = () = > {
    p.then(flushCallbacks)
}

export function nextTick(cb, ctx){
    // Add a callback
    callbacks.push(() = >{
        if(cb) {
            cb.call(ctx)
        }
    })
    if(! pending) { pending =true;
        microTimerFunc()
    }
}
Copy the code

In the code above, we demonstrate the use of microtasks to register callbacks. However, using microtasks can be problematic on some terminals. So vue.js provides a way to degrade macro tasks in special situations.

Priorities are as follows:

Promise.then > setImmediate > MessageChannel > setTimeout

The device on the left is preferred based on the terminal support.

conclusion

To summarize, nextTick registers callbacks to an asynchronous queue. Similarly, DOM updates use asynchronous queues. NextTick uses microtasks by default and demotes them to macro tasks in special cases.