核心思想
当数据发⽣变化的时候,触发set逻辑,把在依赖过程中订阅的的所有观察者,也就是 watcher ,都触发它们的update过程,这个过程⼜利⽤了队列做了进⼀步优化,在 nextTick 后执⾏所有 watcher 的run ,执行了this.get(),最后执⾏它们的回调函数。
set
当我们去修改数据时,会触发数据的set方法。set首先会判断值是否改变,为改变直接return。接着有个逻辑 childOb = !shallow && observe(newVal) ,也就是说如果新值会调用observe如果新值为对象的话它就会接受社会主义洗礼也就是变成响应式。另⼀个是 dep.notify(),通知所有的订阅者派发更新。
dep.notify()
遍历subs,也就是Watcher队列,然后调⽤每⼀个 watcher 的 update ⽅法。
Watcher.update()
这⾥对于 Watcher 的不同状态,会执⾏不同的逻辑, lazy 和 sync 状态后面再说,在⼀般组件数据更新的场景,会⾛到最后⼀个 queueWatcher(this)。
queueWatcher(this)
这⾥引⼊了⼀个队列的概念,这也是Vue在做派发更新的时候的⼀个优化的点,它并不会每次数据改变就立刻触发watcher 的回调,⽽是把这些 watcher 先添加到⼀个队列⾥,然后在 nextTick 后才执⾏flushSchedulerQueue 。
⾸先⽤ has 对象保证同⼀个 Watcher 只添加⼀次;接着对 flushing 的判断,flushing为false时往队列中添加;最后通过 wating 保证对 nextTick(flushSchedulerQueue) 的调⽤逻辑只有⼀次,另外 nextTick 的实现后面再说,⽬前可以理解它是在下⼀个 tick,也就是异步的去执⾏ flushSchedulerQueue 。
flushSchedulerQueue()
- 队列排序
queue.sort((a, b) => a.id - b.id) 对队列做了从⼩到⼤的排序,这么做主要有以下要确保以下 ⼏点:
1.组件的更新由⽗到⼦;因为⽗组件的创建过程是先于⼦的,所以 watcher 的创建也是先⽗后⼦, 执⾏顺序也应该保持先⽗后⼦。
2.⽤户的⾃定义 watcher 要优先于渲染 watcher 执⾏;因为⽤户⾃定义 watcher 是在渲染 watcher 之前创建的。
3.如果⼀个组件在⽗组件的 watcher 执⾏期间被销毁,那么它对应的 watcher 执⾏都可以被跳过,所以⽗组件的 watcher应该先执⾏。
- 队列遍历
在对 queue 排序后,接着就是要对它做遍历,拿到对应的 watcher ,执⾏ watcher.run() 。这⾥需要注意⼀个细节,在遍历的时候每次都会对 queue.length 求值,因为在 watcher.run() 的 时候,很可能⽤户会再次添加新的 watcher ,这样会再次执⾏到 queueWatcher。
可以看到,此时flushing 为 true,就会执⾏到 else 的逻辑,然后就会从后往前找,找到第⼀个待 插⼊ watcher 的 id ⽐当前队列中 watcher 的 id ⼤的位置。把 watcher 按照 id 的插⼊到队列 中,因此 queue 的⻓度发送了变化。
- 状态恢复
这个过程就是执⾏ resetSchedulerState 函数
watcher.run()
执⾏ this.get() ⽅法求值的时候,会执⾏ getter ⽅法。所以这就是当我们去修改组件相关的响应式数据的时候,会触发组件重新渲染的原因,接着就会重新执⾏ patch 的过程。
总结
1.当我们去修改数据时,会触发数据的set方法,首先判断新值是否与旧值相等,相等直接返回。否则对新值调用observe进行社会主义的洗礼,如果新值为对象那么就会为新值添加响应式。接着调用dep.notify()进行依赖的通知。
2.notify主要是遍历subs也就是Watcher队列,然后调⽤每⼀个 watcher 的 update ⽅法。
3.update对于Watcher的不同状态,会执⾏不同的逻辑,lazy 和 sync 状态后面再说,在⼀般组件数据更新的场景,会⾛到最后⼀个queueWatcher(this)。
4.queueWatcher首先获取watcher id,利用对象进行去重保证相同的watcher只能添加一次。一开始flushing属性为false,将watcher添加到queue中。最后通过 wating 保证对 nextTick(flushSchedulerQueue) 的调⽤逻辑只有⼀次,nextTick 的实现后面再说,⽬前可以理解它是在下⼀个 tick,也就是异步的去执⾏ flushSchedulerQueue。
5.flushSchedulerQueue
首先设置flushing属性为true。
其次对queue中的watcher进行排序,排序理由如下:
1.组件的更新由⽗到⼦;因为⽗组件的创建过程是先于⼦的,所以 watcher 的创建也是先⽗后⼦, 执⾏顺序也应该保持先⽗后⼦。
2.⽤户的⾃定义 watcher 要优先于渲染 watcher 执⾏;因为⽤户⾃定义 watcher 是在渲染 watcher 之前创建的。
3.如果⼀个组件在⽗组件的 watcher 执⾏期间被销毁,那么它对应的 watcher 执⾏都可以被跳过,所以⽗组件的 watcher应该先执⾏。
- 接着就是遍历queue调用watcher的run函数。这里注意遍历判断条件是取queue.length的,也就是说在遍历的时候每次都会对 queue.length 求值,因为在 watcher.run() 的 时候,很可能⽤户会再次添加新的 watcher ,这样会再次执⾏到 上面的queueWatcher,而此时flushing属性为true,然后在queue队列中从后往前找,根据watcher ID找到它正确的位置插入。
6.watcher的run方法调用了this.get(),也就触发了this.getter(),最终执行了vm._update(vm._render())进行重新渲染。
7.最后执行resetSchedulerState把这些控制流程状态的⼀些变量恢复到初始值,把 watcher 队列清空。