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();
})();
执行过程:
- 代码定位到最后的立即执行函数,代码至上而下依次执行。
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;- 接下来执行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判断。
- 现在所有的方法执行完毕,就会从任务队列中取出宏任务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方法得到的。