Batch asynchronous update strategy and nextTick principle (code understanding)

Posted by AE117 on Wed, 12 Jan 2022 04:00:23 +0100

let uid = 0;
class Watcher {
    constructor () {
        this.id = ++uid;
    }
    update () {
        console.log('watch' + this.id + ' update');
        queueWatcher(this);
    }
    run () {
        console.log('watch' + this.id + 'View updated~');
    }
}
let callbacks = [];
let pending = false;
function nextTick (cb) {
    callbacks.push(cb);
    if (!pending) {
        pending = true;
        setTimeout(flushCallbacks, 0);
    }
}
function flushCallbacks () {
    pending = false;
    const copies = callbacks.slice(0);
    callbacks.length = 0;
    for (let i = 0; i < copies.length; i++) {
        copies[i]();
    }
}
let has = {};
let queue = [];
let waiting = false;
function flushSchedulerQueue () {
    let watcher, id;
    for (index = 0; index < queue.length; index++) {
        watcher = queue[index]
        id = watcher.id;
        has[id] = null;
        watcher.run();
    }
    waiting  = false;
}
function queueWatcher(watcher) {
    const id = watcher.id;
    if (has[id] == null) {
        has[id] = true;
        queue.push(watcher);
        if (!waiting) {
            waiting = true;
            nextTick(flushSchedulerQueue);
        }
    }
}
(function () {
    let watch1 = new Watcher();
    let watch2 = new Watcher();
    watch1.update();
    watch1.update();
    watch2.update();
})();

Execution process:

  1. The code is positioned to the last immediate execution function, and the code is executed from top to bottom.
    • let watch1 = new Watcher();, Create a new watcher instance object watch1. Locate the watcher class, and this in the constructor points to watch1, watch1 id = 1,uid = 1;
    • let watch2 = new Watcher();, Create a new watcher instance object watch2. Locate the watcher class, and this in the constructor points to watch2, watch2 id = 2,uid = 2;
  2. Next, execute the update method
    • watch1.update();, Locate the Watcher class and execute the update method. First output watch1 update on the console, and then execute the queueWatcher method. The passed in this is watch1.
      • In the queueWatcher method, first, id = 1; Because has[id] == null is true, it will judge if and execute the statement has[id] = true, that is, has = {1: true}, and put the watch1 instance object into the queue array. At this time, queue = [watch1 instance object].
      • Because the waiting is false, it will enter the if judgment again, re assign the waiting to true, then execute the nextTick method, and pass the flushSchedulerQueue method as a parameter into the method.
      • Next, in the nextTick method, first put the flushSchedulerQueue method into the callbacks array. Because pending is false, it will enter the if judgment, and pending will be re assigned to true. However, because setTimeout is a macro task, it will be placed in the task queue first, wait for other functions to execute, and then take the method from the task queue and execute it. Therefore, the second watch1 will be executed first Update method.
    • watch1.update();, Locate the Watcher class and execute the update method. First output watch1 update on the console, and then execute the queueWatcher method. The passed in this is watch1.
      • In the queueWatcher method, first, id = 1; Because has[id] == null is false, the ID judgment is skipped directly.
    • watch2.update();, Locate the Watcher class and execute the update method. First output watch2 update on the console, and then execute the queueWatcher method. The passed in this is watch2.
      • In the queueWatcher method, first, id = 2; Because has[id] == null is true, it will judge if and execute the statement has[id] = true, that is, has = {1: true, 2: true}, and put the watch2 instance object into the queue array. At this time, queue = [watch1 instance object, watch2 instance object].
      • Because waiting is true, the if judgment is skipped.
  3. Now that all the methods are executed, the macro task setTimeout will be taken from the task queue and the flushCallbacks method will be executed.
    • First, pending is assigned false again, and const copies = calls slice(0); callbacks. length = 0;, Assign callbacks to copies and re assign callbacks to an empty array.
    • Secondly, execute the for loop and execute copies[i] ();, Because copies is [flushSchedulerQueue method], the flushSchedulerQueue method is executed.
    • Finally, in the flushscheduler queue method, enter the for loop again. Since queue = [watch1 instance object, watch2 instance object], perform the operations in the for loop in turn, and finally execute the run method to output watch1 view update ~, watch2 view update ~
// results of enforcement
watch1 update
watch1 update
watch2 update
watch1 View updated~
watch2 View updated~

Through the implementation of this code, it can be concluded that when an instance object executes the update method multiple times, the final view will be updated only once. The view completes all update operations only after all update methods are executed, and this implementation is also obtained through the nextTick method.

Topics: Javascript Front-end Vue.js