批量异步更新策略及 nextTick 原理(代码理解)

let uid = 0;
class Watcher {
    constructor () {
        this.id = ++uid;
    }
    update () {
        console.log('watch' + this.id + ' update');
        queueWatcher(this);
    }
    run () {
        console.log('watch' + this.id + '视图更新啦~');
    }
}
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();
})();

执行过程:

  1. 代码定位到最后的立即执行函数,代码至上而下依次执行。
    • let watch1 = new Watcher();,新建Watcher实例对象watch1。定位到Watcher类中,此时构造器中的this指向watch1,watch1.id = 1,uid = 1;
    • let watch2 = new Watcher();,新建Watcher实例对象watch2。定位到Watcher类中,此时构造器中的this指向watch2,watch2.id = 2,uid = 2;
  2. 接下来执行update方法
    • watch1.update();,定位到Watcher类并执行update方法,先在控制台输出watch1 update,然后执行queueWatcher方法,传入的this为watch1。
      • queueWatcher方法中,首先id = 1;因为has[id] == null 为true,因此会进行if判断,并执行里面的语句has[id] = true,即has = {1: true},同时将watch1实例对象放入到queue数组中,此时queue = [watch1实例对象]
      • 又因为waiting为false,因此又会进入if判断,waiting重新赋值为true,然后执行nextTick方法,并将flushSchedulerQueue方法作为参数传入该方法中。
      • 接下来在nextTick方法里,先把flushSchedulerQueue方法放入callbacks数组中,又因为pending为false,所以会进入if判断,pending重新赋值为true。但是因为setTimeout为宏任务,它会先放在任务队列中,等待其他函数执行完毕后,再从任务队列中取出该方法并执行,所以会先去执行第二个watch1.update方法
    • watch1.update();,定位到Watcher类并执行update方法,先在控制台输出watch1 update,然后执行queueWatcher方法,传入的this为watch1。
      • queueWatcher方法中,首先id = 1;因为has[id] == null 为false,因此直接跳过id判断。
    • watch2.update();,定位到Watcher类并执行update方法,先在控制台输出watch2 update,然后执行queueWatcher方法,传入的this为watch2。
      • queueWatcher方法中,首先id = 2;因为has[id] == null 为true,因此会进行if判断,并执行里面的语句has[id] = true,即has = {1: true, 2: true},同时将watch2实例对象放入到queue数组中,此时queue = [watch1实例对象,watch2实例对象]。
      • 又因为waiting为true,因此又跳过if判断。
  3. 现在所有的方法执行完毕,就会从任务队列中取出宏任务setTimeout,并执行flushCallbacks方法。
    • 首先pending再次被赋值为false,执行const copies = callbacks.slice(0);callbacks.length = 0;,将callbacks赋值给copies,并将callbacks重新赋值为空数组。
    • 其次执行for循环,并执行copies[i]();,因为copies为[flushSchedulerQueue方法],所以就是执行flushSchedulerQueue方法。
    • 最后,在flushSchedulerQueue方法中,再次进入for循环,由于queue = [watch1实例对象,watch2实例对象],依次进行for循环里的操作,最终执行run方法,分别输出watch1视图更新啦~、watch2视图更新啦~
// 执行结果
watch1 update
watch1 update
watch2 update
watch1视图更新啦~
watch2视图更新啦~

通过这段代码的实现可以得出,当某个实例对象执行多次update方法后,最终视图只会更新一次。而视图完成全部的更新操作,是等待所有的update方法执行完毕后,才会进行,而这一实现也是通过nextTick方法得到的。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值