说说Vue的几个watcher(一)——render watcher

9,075 阅读3分钟

本文仅从理论的角度来阐述Vue.js中的响应式,不涉及具体源码。

前言

我们都知道Vue.js具有数据响应式的特点。

常见的场景有下面这几个:

  • 数据变 → 使用数据的视图变
  • 数据变 → 使用数据的计算属性变 → 使用计算属性的视图变
  • 数据变 → 开发者主动注册的watch回调函数执行

三个场景,对应三种watcher:

  • 负责敦促视图更新的render-watcher
  • 执行敦促计算属性更新的computed-watcher
  • 用户注册的普通watcher(watch-api或watch属性)

我们用三篇文章来看看这三种watcher都是什么、干什么用、以及怎么用的。

watcher系列文章在此,本篇是第一篇

render watcher

new Vue({
  template: `<div>My name is {{name}}<div>`,
  data: {
    name: 'FooBar',
    gender: 'male'
  }
})

结合上面的代码来看,响应式意味着:当name属性值改变时,渲染的内容也应随之变化。

建立联系

如何才能建立视图渲染与属性值之间的联系?先来搞清楚两个问题

  • 用了这个数据
  • 数据变了之后怎么办

在视图渲染这个场景下,这两个问题的解答分别是:

  • 负责生成视图的render函数要用这个数据
  • 数据变了得执行render函数

数据劫持

用了变了,是可以通过对该属性值设置访问描述符(get/set)知道的。

因此,需要遍历所有data属性值,用Object.defineProperty设置访问描述符(get/set)。

  • 谁用了这个数据?
    触发了属性值get的就是要用到的,应该在getter里记录下使用者。

  • 数据变了怎么办?
    数据变就会触发属性值set,应该在setter里告知使用者。

订阅-发布

从上面的描述可以看出,这个场景是典型的发布&订阅。

在视图渲染的场景中,render-watcher是订阅者。每个属性值都有一个依赖管理者——dep,负责记录和通知订阅者。

依赖的收集与通知

收集订阅(依赖)者的流程

  1. 订阅者执行回调(render函数)
  2. 触发属性值getter
  3. 添加到订阅者队列
  4. 重复2、3直至所有getter执行完

通知订阅者的流程

  1. 属性改变
  2. 触发属性值setter
  3. dep通知订阅者(render watcher)
  4. 订阅者执行回调(render函数)

取消订阅

当某些属性值不再被视图使用的时候,就应该取消掉对这些属性的订阅。

怎么才能知道哪些属性值不再被引用呢?我们可以这么做:

订阅者(render-watcher)也维护一个依赖集合,将依赖的属性值的dep存储在这个集合里。

每当render function执行一次,也就是触发属性值的getter时,订阅者(render-watcher)会存储一份新的依赖集合。对比新旧依赖集合,找出已经不再依赖的旧dep,将render-watcher从这个旧dep的订阅者队列中删除。这样就不会通知到当前的订阅者了(render-watcher)。