持续创作,加速成长!这是我参与「掘金日新计划 · 10 月更文挑战」的第22天,点击查看活动详情
在上三篇文章vue2关于响应式原理源码详解(1)中主要了解了vue的响应式原理,在vue2关于响应式原理源码详解(2)- Dep、Watcher中主要了解了:Dep(观察目标类),Watcher(观察者类),在# vue2关于响应式原理源码详解(3)- 依赖收集中了解了依赖收集的具体流程。本文着重讲解派发更新的具体流程。
派发更新流程
/src/core/observer/dep.ts
此时,模拟当有响应式数据发生改变。会触发set()方法,调用dep.notify()。
/src/core/observer/watcher.ts
触发update方法,如果不是懒执行和同步执行,会进入queueWatcher()方法。派发更新的重点。
/src/core/observer/scheduler.ts
接着重点看queueWatcher()方法和flushSchedulerQueue()方法。
function queueWatcher (watcher) {
var id = watcher.id;
//去重,相同id的watcher不会再次推进队列中
if (has[id] == null) {
has[id] = true;
if (!flushing) {
queue.push(watcher); //将watcher推入队列中
}
else {
var i = queue.length - 1;
while (i > index && queue[i].id > watcher.id)
{
i--;
}
queue.splice(i + 1, 0, watcher);
} if (!waiting) {
waiting = true;
nextTick(flushSchedulerQueue); //异步刷新队列
}
}
}
function flushSchedulerQueue () {
flushing = true;
var watcher, id;
queue.sort(function (a, b) { return a.id - b.id; }); //对队列做了从⼩到⼤的排序
for (index = 0; index < queue.length; index++) {
watcher = queue[index];
id = watcher.id;
has[id] = null;
watcher.run();
}
const activatedQueue = activatedChildren.slice();
const updatedQueue = queue.slice() ;
resetSchedulerState();//清状态
}
queueWatcher方法中,
首先对watcher id 进行了去重判断,因为在一个 renderWatch 中可能会依赖多个观察目标,当我们同时改变多个依赖的值 ,如果 watcher.id 一样的话就不用把两次更新都push到队列中,避免渲染性能消耗。
引入了一个队列的概念,其中flushing 表示 queue 队列的更新状态,flushing为true则表示队列正在更新中。当flushing为false时将watcher推入队列中,最后通过对wating保证对nextTick(flushSchedulerQueue)的调用逻辑只有一次。flushSchedulerQueue 方法是一个微任务。等宏任务结束后调用微任务队列,然后调用flushSchedulerQueue的回调。
flushSchedulerQueue方法中,
通过queue.sort()对队列进行从小到大的排序,是因为
1.父子组件的创建是先父后子,因此watcher的创建和执行顺序也应该是先父后子。而父组件的watcher
.id是小于子组件的。
2.用户的自定义watcher的执行和创建应该要优先于渲染watcher
3.如果子组件在父组件watcher执行过程中被销毁,那么需要跳过子组件的watcher,因此父组件要先被执行。
排序后,则对队列进行遍历操作,拿到对应的watcher,执行watcher.run。
/src/core/observer/watcher.ts
执行watcher.run,在该方法中会执⾏ this.get() ⽅法求值的时候,会执⾏ getter ⽅法。即会触发组件重新渲染的原因,接着就会重新执⾏ patch 的过程。
总结
- 触发 setter 的时候会调用
dep.notify()通知所有订阅者进行派发更新 - 触发更新时调用update
- 触发
queueWatcher()方法,这是一个队列,把这些 watcher 先添加到⼀个队列⾥,然后在 nextTick 后才执⾏flushSchedulerQueue。 - 在 nextTick 后执⾏所有 watcher 的run ,执行了this.get(),最后执⾏它们的回调函数。
- 执⾏ this.get() ⽅法求值的时候,会执⾏ getter ⽅法。即会触发组件重新渲染的原因,接着就会重新执⾏ patch 的过程。
大致过程如下: set -> dep.notify() -> subs[i].update() -> watcher.run() || queueWatcher(this) -> watcher.get() || watcher.cb -> watcher.getter() -> vm._update() -> vm.patch()