本文仅从理论的角度来阐述Vue.js中的响应式,不涉及具体源码。
前言
我们都知道Vue.js具有数据响应式的特点。
常见的场景有下面这几个:
- 数据变 → 使用数据的视图变
- 数据变 → 使用数据的计算属性变 → 使用计算属性的视图变
- 数据变 → 开发者主动注册的watch回调函数执行
三个场景,对应三种watcher:
- 负责敦促视图更新的render-watcher
- 执行敦促计算属性更新的computed-watcher
- 用户注册的普通watcher(watch-api或watch属性)
我们用三篇文章来看看这三种watcher都是什么、干什么用、以及怎么用的。
watcher系列文章在此,本篇是第一篇
- 说说Vue的几个watcher(一)——render watcher
- 说说Vue的几个watcher(二)——computed watcher
- 说说Vue的几个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,负责记录和通知订阅者。
依赖的收集与通知
收集订阅(依赖)者的流程
- 订阅者执行回调(render函数)
- 触发属性值getter
- 添加到订阅者队列
- 重复2、3直至所有getter执行完
通知订阅者的流程
- 属性改变
- 触发属性值setter
- dep通知订阅者(render watcher)
- 订阅者执行回调(render函数)
取消订阅
当某些属性值不再被视图使用的时候,就应该取消掉对这些属性的订阅。
怎么才能知道哪些属性值不再被引用呢?我们可以这么做:
订阅者(render-watcher)也维护一个依赖集合,将依赖的属性值的dep存储在这个集合里。
每当render function执行一次,也就是触发属性值的getter时,订阅者(render-watcher)会存储一份新的依赖集合。对比新旧依赖集合,找出已经不再依赖的旧dep,将render-watcher从这个旧dep的订阅者队列中删除。这样就不会通知到当前的订阅者了(render-watcher)。