I happened to see an article about nextTick method by a big man, and ALTHOUGH I used it often before, I never tried to understand the principle. Then say dry dry, do not see do not know, a look unexpectedly found that the middle will involve Vue asynchronous update DOM mechanism, simply too fierce 😻, quickly record!

We all know that Vue is the most basic two-way binding principle, the specific content will not be studied in detail here. But diffusion of some functions and knowledge is also the essence.

A very small process that triggers the update mechanism is recorded here. Self study record from rookie front end, hope big guy points out mistake! 😆)

1, mountComponent

With the update mechanism in mind, let’s take a quick look at the code that defines Watcher. Because it’s ultimately the key to triggering the DOM update operation. When traversing the virtual node to browser-aware DOM elements, the watch object is mounted for the corresponding label element, and Deps (a collection of notifications to Watcher) notifies Watcher to perform the update method when data changes.

function mountComponent (
/ /...
new Watcher(vm, updateComponent, noop, {
    before: function before () {
        if(vm._isMounted && ! vm._isDestroyed) { callHook(vm,'beforeUpdate'); }}},true /* isRenderWatcher */);
/ /...
}
Copy the code

2, Watcher. Prototype. The update

The focus here is on the asynchronous rendering to the DOM process when data changes, but the rest will not be outlined.

Watcher.prototype.update = function update () {
    /* istanbul ignore else */
    if (this.lazy) {
        this.dirty = true;
    } else if (this.sync) {
        this.run();
    } else { // Normal flow will run over here
        console.log('Trigger update on watcher')
        queueWatcher(this);
    }
Copy the code

Excluding the lazy and sync attributes of Watcher, the queueWatcher method is run and the current Watcher is passed into the function. So as we move on, it feels like we’re getting closer and closer to the core.

3, queueWatcher

function queueWatcher (watcher) {
    console.log('the current watcher',watcher)
    var id = watcher.id; // Assigns the id of the current watcher
    if (has[id] == null) { // Use a has object to record the watcher under the current loop
        has[id] = true;// flag when a watcher with this id has been saved
        console.log('flushing state',flushing)
        if(! flushing) {// The flushing function is used to record whether the queues are currently running
            queue.push(watcher); // Insert watcher into a queue, notice that the asynchronous update operation has already started, because there is no previous run on this side
            console.log('Value of queue after putting it in watcher',queue)
        } else {
            // if already flushing, splice the watcher based on its id
            // if already past its id, it will be run next immediately.
            var i = queue.length - 1;
            while (i > index && queue[i].id > watcher.id) {
                i--;
            }
            queue.splice(i + 1.0, watcher);
        }
        // queue the flush
        console.log('waiting state',waiting)
        if(! waiting) {console.log('If false, set to true')
            waiting = true; // Each time the loop starts with waiting set to true, the next update method will not call the following method. To put it more simply, the method of executing a loop queue is pushed, microtasks or otherwise, to the normal code (after macro tasks) to solve the problem of frequent refreshes.
            if(process.env.NODE_ENV ! = ='production' && !config.async) {
                flushSchedulerQueue();
                return
            }
            console.log('Waiting =true, call nextTick, let flushSchedulerQueue execute later')
            nextTick(flushSchedulerQueue); // Call nextTick to put the flushSchedulerQueue in it}}Copy the code

Field Description:

✔ ️ from:

Here, a HAS object is used to record the watcher under the current loop. Only when this item is not recorded, it will continue to go down, so as to avoid triggering the same attribute for multiple times to avoid repetition.

✔️while (I > index && queue[I].id > watcher.id) :

I looked here for a long time, and I didn’t realize anything until I looked back.

And we’re going to talk about flushSchedulerQueue, which, when it runs, puts flushing to true, and puts the queue in ascending order. So at the same time as flushSchedulerQueue, if a new watcher comes in, the function of this method is to set I to the maximum value in the array and subtract I by one until I is equal to index. Insert the current Watcher here and immediately run it as the next one.

4, flushSchedulerQueue

Let’s look at the flushSchedulerQueue method first, and then the Nextick method.

function flushSchedulerQueue () {
    console.log('flushSchedulerQueue internal')
    console.log('Flushing is true, so that's what's going on in the current queue.')
    currentFlushTimestamp = getNow();
    flushing = true; // When we run this method, the flushing state is true.
    var watcher, id;
    console.log('Final contents of this loop queue',queue)
    // Sort queue before flush.
    queue.sort(function (a, b) { return a.id - b.id; });
    for (index = 0; index < queue.length; index++) { // Run the queue array
        watcher = queue[index];
        if (watcher.before) {
            watcher.before();
        }
        id = watcher.id;
        has[id] = null;
        watcher.run();
        // in dev build, check and stop circular updates.
        / /... Leave out some code
    }

    // keep copies of post queues before resetting state
    var activatedQueue = activatedChildren.slice();
    var updatedQueue = queue.slice();

    resetSchedulerState(); // Reset the property

    // call component updated and activated hooks
    callActivatedHooks(activatedQueue); // Actived cycles, paired with keep-alive
    callUpdatedHooks(updatedQueue);

    // devtool hook
    /* istanbul ignore if */
    if (devtools && config.devtools) {
        devtools.emit('flush'); }}Copy the code

Thus, the flushSchedulerQueue method is the final purpose of iterating through the watcher stored in queues, except that it waits for the macro code in the current loop to finish running (in short, the process of shoehorting values into queues) and then resets some attributes.

5, nextTick

As directly on the source code

function nextTick (cb, ctx) {
    console.log('Inside nextTick, store methods in callbacks')
    var _resolve;
    callbacks.push(function () {  // Here we pass the CB callback method into the Callbacks array
        if (cb) {
            try {
                cb.call(ctx);
            } catch (e) {
                handleError(e, ctx, 'nextTick'); }}else if(_resolve) { _resolve(ctx); }});console.log('callbacks:',callbacks)
    console.log('pedding state',pending)
    if(! pending) {// Pending is used to control the state, which is discussed in more detail below
        console.log('pedding set to'.true)
        pending = true;
        timerFunc();
    }
    // $flow-disable-line
    if(! cb &&typeof Promise! = ='undefined') {
        return new Promise(function (resolve) { _resolve = resolve; }}})Copy the code

Field Description:

✔ ️ pedding

Pending: Pending should be for multiple nextTick methods. When the first nextTick runs in the current update, the corresponding callback is placed in the Callbacks array, waiting for promise.then(the microtask is invoked).

If there is a next nextTick method, to prevent it from executing promise.then on the same callbacks array, use pending. When pending is true, subsequent nextTick simply puts the callback into the Callbacks array and does not wait for it to run, but instead waits for the first Promise to be invoked. (The result of an example is shown below)

Only one more timerFunc method left. Come on! 😄

var timerFunc;
if (typeof Promise! = ='undefined' && isNative(Promise)) {
    var p = Promise.resolve();
    timerFunc = function () {
        console.log('将callbacks放入promise.then里面去运行')
        p.then(flushCallbacks); // The promise.then () implementation is used to adjust the order in which tasks are called
// Also, the flushCallbacks array is a copy of the callbacks.
        if (isIOS) { setTimeout(noop); }}; isUsingMicroTask =true;
} else if(! isIE &&typeofMutationObserver ! = ='undefined' && (
    isNative(MutationObserver) ||
    // PhantomJS and iOS 7.x
    MutationObserver.toString() === '[object MutationObserverConstructor]'
)) {
//...
// Some irrelevant code is omitted here to determine if the current browser does not support Promise
}
Copy the code

The method here is simply the place to actually run the callbacks method, but control the timing. Over here, the whole process is actually quite simple, but it’s really about the role of some of the variables that you have to think about, and once you straighten it out, it’s actually quite simple. (Understanding is only the first step, there is a long way to go.)

A final summary:

Trigger the watcher.prototype. update method ⏩ run the queueWatcher method (the procedure for storing and controlling state) ⏩ define the method that finally executes the queue method in flushSchedulerQueue ⏩ The above method uses nextTick to control when to execute to achieve the effect of asynchronous update.

There’s still a lot to learn, so keep going. 🌼 🌼 🌼

then

Therefore, it can be understood that sometimes when the value of data is changed and the corresponding DOM value is obtained, vue. $nextTick method is needed to ensure that watcher’s DOM update method is completed before the value is obtained.

Such as:

<div>{{msg}}</div>
//msg: 'a'
this.msg = 'b'
console.log(this.$refs.msgRef.innerHTML,'after change')
this.$nextTick(() = >{
    console.log(this.$refs.msgRef.innerHTML,'after $nextTick')})Copy the code

Results:

Extensions: About macro and micro tasks

Macro task:

It is understood that the code executed by each execution stack is a macro task.

  • Script (whole code)
  • setTimeout
  • setInterval
  • I/O UI interaction events
  • postMessage
  • MessageChannel
  • SetImmediate (Node. Js environment)

Micro tasks:

A microtask is a task that is executed immediately after the execution of the current task. That is, after the current task, before the next task, and before rendering.

  • Promise.then
  • Object.observe
  • MutationObserver
  • Process. NextTick (Node. Js environment)

Here is no more explanation, as long as you know that the running order is script ⏩ Promise. Then ⏩ setTimeout, I guess only newbies like me just understand these things 😭.

Add two cases of running print screenshots (print location above source can be seen)

1. When modifying a value

this.msg = 'a' // Change only one parameter

Copy the code

The results of

2. The above case of nextTick proves the function of pending in the nextTick function.

The results of