这是我参与8月更文挑战的第22天,活动详情查看:8月更文挑战
前言
理解 Vue 响应式原理,我个人比较推荐的方法是结合源码来看。Vue响应式原理,你所需要知道的 首先,各位再熟悉不过的,一定是 Vue 官方提供的这张示意图了:我们以这张图为基础,先帮助大家重新捋一遍响应式的机制。在这个基础上,再去做更进一步的分析。注意我们图中有三个关键角色:Watcher、Data、和 Render。
Vue 会对传入的 data 做处理:为每一个属性添加 getter 和 setter。在这个过程中,涉及到了 Object.defineProperty 这个方法。
同时每一个 Vue 组件实例,都对应着一个 watcher 实例;这个 watcher 实例仿佛一个跟踪狂,它的目光永远跟随着 data: 由于 render 函数的执行依赖于数据的读取,因此渲染时必定会读取 data 属性进而触发其对应的 getter 方法。getter 方法被调用后,会通知到 watcher,watcher 就会把这些 getter 方法被触发的属性记录为“依赖”—— 这一过程,就是大家常常听到的“依赖收集”过程。
如果 data 发生了更新,也就是说被“写”了,此时对应属性的 setter 方法就会被触发。setter 也会去通知 watcher, 告诉它“我改变了”。watcher 拿到消息后,立刻跑去告诉 render:“data变了,你也给我跟着变!”。由此去触发一个 re-render 的过程、与数据更新相关的组件会重新渲染。
源码分析
Object.defineProperty
Object.defineProperty()
方法会直接在一个对象上定义一个新属性,或者修改一个对象的现有属性,并返回这个对象。它的调用形式如下:
Object.defineProperty(obj, prop, descriptor)
其中第一个入参,是我们操作的目标对象;第二个入参,是我们需要修改的属性的名称;第三个入参,是一个描述符,用来描述你到底要对这个目标属性做什么。
我们在 Vue 响应式原理中涉及到的“描述符”,就是 getter/setter 方法:
getter
方法: 一个给属性提供 getter 的方法,实际方法名为“get”;如果没有 getter 则为 undefined 。当访问该属性时,该方法会被执行,方法执行时没有参数传入,但是会传入this 对象(由于继承关系,这里的this 并不一定是定义该属性的对象)。默认为 undefined。
setter
方法: 一个给属性提供 setter 的方法,实际方法名为“set”;如果没有 setter 则为 undefined 。当属性值修改时,触发执行该方法。该方法将接受唯一参数,即该属性新的参数值。默认为 undefined。
Observer、Dep 和 Watcher 的关系
在源码层次,大家需要把握好这三个角色:
Observer
:处理 data 的家伙。它会给 data 安装 getter 和 setter,这些安装上的逻辑会联动 Dep 去完成依赖收集和更新的派发;
Dep
:实际通知 Watcher 的人。在 getter 和 setter 逻辑中,正是通过调度 Dep 来完成信息的收集、以及和Watcher 间的通信;
Watcher
:Watcher 被通知之后,就会通知 render、进而触发重渲染了。
Observer
Observer 的作用是遍历所有的属性,给它们安装上 getter/setter 方法:
class Observer {
constructor() {
// 具体逻辑在 observe 函数里
observe(this.data);
}
}
function observe (data) {
// 取出所有的 key
const keys = Object.keys(data);
// 遍历所有属性
for (let i = 0; i < keys.length; i++) {
// 绑定 getter/setter 方法
defineReactive(obj, keys[i]);
}
}
这里我们看到,具体的绑定操作是在 defineReactive 里做的:
function defineReactive (obj, key, val) {
// 定义一个 Dep 对象,它的作用正如我们上文所说const dep = new Dep();
Object.defineProperty(obj, key,
{ enumerable: true,
configurable: true,
get() {
// 收集依赖、关联到 watcher dep.depend();
return val;
},
set(newVal) {
if (newVal === val) return;
// 感知更新、通知 watcher
dep.notify();
}
});
}
在 defineReactive 里面,每一个 getter/setter 里面都出现了 Dep 实例。正如我们前面所介绍的一样,实际收集信息和通知 watcher 的工作是 Dep 来做的。每一个属性都对应一个单独的 Dep 实例。
在 getter 方法里面,调用了 dep 的 depend 方法,这个方法有什么玄机呢?我们来看看 Dep 的结构:
Dep
Dep 的角色,宛如一个“工具人”,它是 Watcher 和 Observer 之间的纽带,是“通信兵”:
class Dep {
constructor () {
// 存储 Watcher 实例的数组
this.subs = []
}
// 将 watcher 实例添加到 subs 中(这个方法在 Watcher 类的实现里会用到)
addSub (sub: Watcher) {
this.subs.push(sub)
}
// 收集依赖
depend() {
// Dep.target 实际上就是当前 Dep 对应的 watcher,我们下文会提及
if (Dep.target) {
// 把当前的 dep 实例关联到组件对应的 watcher 上去
Dep.target.addDep(this)
}
}
// 通知 watcher 对象发生更新
notify () {
const subs = this.subs.slice()
// 这里 subs 的元素是 watcher 实例,逐个调用 watcher 实例的 update 方法
for (let i = 0, l = subs.length; i < l; i++) {
subs[i].update()
}
}
}
在 Dep 内部,会维护一个 watcher 队列。
depend 方法在每次 getter 触发时都会把 watcher 实例和 dep 实例做一次关联。
在 setter 触发时,dep 实例便会逐个通知每一个和自己有关联的 watcher:我对应的属性发生了更新!进而调度watcher 实例的 update 方法,实现视图更新。
Watcher
class Watcher {
constructor() {
...
// Dep 的 target 属性是有赋值过程的^_^,它是组件对应的 watcher 对象
Dep.target = this
...
}
addDep (dep: Dep) {
...
// 把当前的 watcher 推入 dep 实例的 watcher 队列(subs)里去
dep.addSub(this)
...
}
update() {
// 更新视图
}
}
这里需要大家注意一点:宏观上看,咱们说“收集依赖”,是指 watcher 去收集自己所依赖的数据属性;不过从实现上来看,实际上是把 watcher 对象推入了 dep 实例的队列里,更像是 dep 在“收集” watcher。
其实,不管是谁来维护队列、谁“收集”谁,其本质目的都是建立起 dep 和 watcher 间的关联,达到 dep 发生变化后可以立刻通知到 watcher 的目的。
常见问题FAQ
- 免费下载或者VIP会员专享资源能否直接商用?
- 本站所有资源版权均属于原作者所有,这里所提供资源均只能用于参考学习用,请勿直接商用。若由于商用引起版权纠纷,一切责任均由使用者承担。更多说明请参考 VIP介绍。
- 提示下载完但解压或打开不了?
- 找不到素材资源介绍文章里的示例图片?
- 模板不会安装或需要功能定制以及二次开发?
发表评论
还没有评论,快来抢沙发吧!