异步更新队列
-
有三种watcher
- render watcher,改变了就可以直接更改视图
- computed watcher,计算之后改变视图
- 普通的watcher,这个是啥?
-
异步队列
- 当一个data连续改变十次,那么watcher是要更新十次吗?
- watcher是一执行update就会执行吗?
-
多个watcher的执行顺序是怎么样的?都是先到先得吗?
——并不是,官方定义了执行先后顺序的三条规则,用id进行排序,那么这个 id 是怎么分配的呢?
- 先更新父组件再更新子组件,因为父组件比子组件先创建
- 先更新一个组件的
user watchers再更新他的render watchers - 如果一个组件在父组件被更新的时候被销毁,那么跳过这个组件
——id 是如何分配的?在watcher创建之初会赋值一个id,id越大,说明越年轻,按id进行排序就可以保证上面三条规则
——render watcher 和 use watcher 是什么时候定义的?
Vue 在更新 DOM 时是异步执行的。只要侦听到数据变化,Vue 将开启一个队列,并缓冲在同一事件循环中发生的所有数据变更。如果同一个 watcher 被多次触发,只会被推入到队列中一次。这种在缓冲时去除重复数据对于避免不必要的计算和 DOM 操作是非常重要的
-
异步队列方法
queueWatcher- 先通过id判断是否已经在队列中
- 如果没有就放进队列中,flushing
在下次 DOM 更新循环结束之后执行延迟回调。在修改数据之后立即使用这个方法,获取更新后的 DOM。
flushSchedulerQueue 的作用
1、升序排列 watcher 更新队列
2、遍历 watcher 更新队列,然后逐个调用 watcher 更新
3、watcher 更新队列执行完毕,重置状态
// Sort queue before flush.
// This ensures that:
// 1. Components are updated from parent to child. (because parent is always
// created before the child)
// 2. A component's user watchers are run before its render watcher (because
// user watchers are created before the render watcher)
// 3. If a component is destroyed during a parent component's watcher run,
// its watchers can be skipped.
为什么要升序排列呢?
export function queueWatcher (watcher: Watcher) {
const id = watcher.id
if (has[id] == null) {
has[id] = true
if (!flushing) {
queue.push(watcher)
} else {
// if already flushing, splice the watcher based on its id
// if already past its id, it will be run next immediately.
let i = queue.length - 1
while (i > index && queue[i].id > watcher.id) {
i--
}
queue.splice(i + 1, 0, watcher)
}
// queue the flush
if (!waiting) {
waiting = true
if (process.env.NODE_ENV !== 'production' && !config.async) {
flushSchedulerQueue()
return
}
nextTick(flushSchedulerQueue)
}
}
}
在哪个阶段生成
initState 阶段就有
initData 阶段就有dep
$mount 阶段就 new Watcher
如果他在模版中被调用,那它一定有一个渲染Watcher
dirty 和 computed 的缓存
Proxy
我们来看看proxy
Proxy 对象用于定义基本操作的自定义行为(如属性查找,赋值,枚举,函数调用等)
可以理解为在对象之前设置一个”拦截“,当监听的对象被访问的时候,都必须经过这层拦截。可以在这拦截中对原对象处理,返回需要的数据格式,也就是无论访问对象的什么属性,之前定义的或是新增的属性,都会走到拦截中进行处理。这就解决了之前所无法监听的问题。
新增或编辑属性,并不需要重新添加响应式处理,都能监听的到,因为 Proxy 是对对象的操作,只要你访问对象,就会走到 Proxy 的逻辑中。Reflect 是一个内置的对象,它提供拦截 JavaScript 操作的方法。这些方法与proxy handlers的方法相同。Reflect不是一个函数对象,因此它是不可构造的,区别Proxy 和 Object.defineProperty 的使用方法看似很相似,其实 Proxy 是在更高维度上去拦截属性的。
Object.definePropertyVue2 中,对于给定的 data:如 { count: 1 },是需要根据具体的 key 也就是 count,去对 get 和 set 进行拦截,也就是:
必须预先知道要拦截的 key 是什么,这也就是为什么 Vue2 里对于对象上的新增属性无能为力,所以 Vue 初始化的过程中需要遍历 data 来挟持数据变化,造成速度变慢,内存变大的原因。
Proxy 而 Vue3 所使用的 Proxy,则是这样拦截的:
new Proxy(data, {
get(key) { },
set(key, value) { },
})
可以看到,proxy 不需要关心具体的 key,它去拦截的是修改 data 上的任意 key和读取 data 上的任意 key
所以,不管是已有的 key 还是新增的 key,都会监听到。
computed
\