关于watcher的更加具体的内容

386 阅读4分钟

异步更新队列

  1. 有三种watcher

    • render watcher,改变了就可以直接更改视图
    • computed watcher,计算之后改变视图
    • 普通的watcher,这个是啥?
  2. 异步队列

    • 当一个data连续改变十次,那么watcher是要更新十次吗?
    • watcher是一执行update就会执行吗?
    • 多个watcher的执行顺序是怎么样的?都是先到先得吗?

      ——并不是,官方定义了执行先后顺序的三条规则,用id进行排序,那么这个 id 是怎么分配的呢?

      • 先更新父组件再更新子组件,因为父组件比子组件先创建
      • 先更新一个组件的 user watchers 再更新他的 render watchers
      • 如果一个组件在父组件被更新的时候被销毁,那么跳过这个组件

      ——id 是如何分配的?在watcher创建之初会赋值一个id,id越大,说明越年轻,按id进行排序就可以保证上面三条规则

      ——render watcher 和 use watcher 是什么时候定义的?

  3. zhuanlan.zhihu.com/p/72494035

Vue 在更新 DOM 时是异步执行的。只要侦听到数据变化,Vue 将开启一个队列,并缓冲在同一事件循环中发生的所有数据变更。如果同一个 watcher 被多次触发,只会被推入到队列中一次。这种在缓冲时去除重复数据对于避免不必要的计算和 DOM 操作是非常重要的

  • 异步队列方法queueWatcher

    1. 先通过id判断是否已经在队列中
    2. 如果没有就放进队列中,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 进行拦截,也就是:

www.itheima.com/news/202008…

必须预先知道要拦截的 key 是什么,这也就是为什么 Vue2 里对于对象上的新增属性无能为力,所以 Vue 初始化的过程中需要遍历 data 来挟持数据变化,造成速度变慢,内存变大的原因。

Proxy 而 Vue3 所使用的 Proxy,则是这样拦截的:

new Proxy(data, {
  get(key) { },
  set(key, value) { },
})

可以看到,proxy 不需要关心具体的 key,它去拦截的是修改 data 上的任意 key和读取 data 上的任意 key

所以,不管是已有的 key 还是新增的 key,都会监听到。

computed

juejin.cn/post/689793…

\