【前端进阶基础】VUE响应式数据原理 订阅-发布模式解析

5次阅读

共计 3024 个字符,预计需要花费 8 分钟才能阅读完成。

vue 框架的两个抽象核心:虚拟 DOM 和相应式数据原理
关于虚拟 DOM 的核心算法,我们上一章已经基本解析过了,详细的见 React && VUE Virtual Dom 的 Diff 算法统一之路 snabbdom.js 解读
关于响应式数据原理,我们先看张图你‘(4).png
具体来讲,要分以下几步:

初始化实例对象时运行 initState, 建立好 props, data 的钩子以及其对象成员的 Observer, 对于 computed 属性,则建立起所有对应的 Watcher 并且通过 Object.defineProperty 在 vm 对象上设置一个该属性的 getter。同时还根据自定义的 watch 来建立相应的 Watcher
执行挂载操作,在挂载时建立一个直接对应 render(渲染)的 Watcher,并且编译模板生成 render 函数,执行 vm._update 来更新 DOM。
此后每当有数据改变,都将通知相应的 Watcher 执行回调函数。

Observer 劫持者
export class Observer {
value: any;
dep: Dep;
vmCount: number; // number of vms that has this object as root $data

constructor (value: any) {
this.value = value
this.dep = new Dep()
this.vmCount = 0
def(value, ‘__ob__’, this)
if (Array.isArray(value)) {
const augment = hasProto
? protoAugment
: copyAugment
augment(value, arrayMethods, arrayKeys)
this.observeArray(value)
} else {
this.walk(value)
}
}

/**
* Walk through each property and convert them into
* getter/setters. This method should only be called when
* value type is Object.
*/
walk (obj: Object) {
const keys = Object.keys(obj)
for (let i = 0; i < keys.length; i++) {
defineReactive(obj, keys[i], obj[keys[i]])
}
}

/**
* Observe a list of Array items.
*/
observeArray (items: Array<any>) {
for (let i = 0, l = items.length; i < l; i++) {
observe(items[i])
}
}
}
首先我们观察到,new Observer() 的时候,会进行类型的判断
if (Array.isArray(value)) {
const augment = hasProto
? protoAugment
: copyAugment
augment(value, arrayMethods, arrayKeys)
this.observeArray(value)
} else {
this.walk(value)
}
如果是数组类型则会递归调用,建立依赖体系否则则调用 walk()函数去利用 Object.defineProperty 简历依赖
那么 defineReactive 是如何实现的呢,如下
// 省略了部分代码
export function defineReactive (
obj: Object,
key: string,
val: any,
customSetter?: ?Function,
shallow?: boolean
) {
const dep = new Dep()

Object.defineProperty(obj, key, {

get: function reactiveGetter () {

dep.depend()

return value
},
set: function reactiveSetter (newVal) {

dep.notify()
}
})
}
拦截器会分别在 getter 和 setter 上设置中间件去维护 dep 数组中的依赖关系

dep.depend()
dep.notify()

字面意思非常明显,分别用于增加依赖和通知依赖变化关系
Dep (dependency // 依赖)
class Dep {
static target: ?Watcher;
id: number;
subs: Array<Watcher>;

constructor () {
this.id = uid++
this.subs = []
}

addSub (sub: Watcher) {
this.subs.push(sub)
}

removeSub (sub: Watcher) {
remove(this.subs, sub)
}

depend () {
if (Dep.target) {
Dep.target.addDep(this)
}
}

notify () {
// stabilize the subscriber list first
const subs = this.subs.slice()
for (let i = 0, l = subs.length; i < l; i++) {
subs[i].update()
}
}
}
Dep 就是一个 Watcher 所对应的数据依赖,在这个对象中也存有一个 subs 数组,用来保存和这个依赖有关的 Watcher。其成员函数最主要的是 depend 和 notify,前者用来设置某个 Watcher 的依赖,后者则用来通知与这个依赖相关的 Watcher 来运行其回调函数。
我们知道,Dom 上通过指令或者双大括号绑定的数据,会为数据进行添加观察者 watcher,当实例化 Watcher 的时候 会触发属性的 getter 方法,此时会调用 dep.depend()。我们看一下 depend 方法:
depend () {
if (Dep.target) {
Dep.target.addDep(this)
}
}
Dep.target 是什么东西呢?其实在进行 Watcher 实例化的时候,会调用内部的 get 函数,开始为他初始化
Watcher 观察者
其中 pushTarget 方法就是为 Dep.target 绑定此 watcher 实例,所以 Dep.target.addDep(this) 也就是执行此实例中的 addDep 方法
addDep (dep: Dep) {

dep.addSub(this)
}
这样便为我们的 dep 实例添加了一个 watcher 实例。
接着当我们更新 data 的时候,会触发他的 set 方法,执行 dep.notify() 函数:
notify () {
// stabilize the subscriber list first
const subs = this.subs.slice()
for (let i = 0, l = subs.length; i < l; i++) {
subs[i].update()
}
}
这里也就是遍历 dep 中收集到的 watcher 实例,进行 update()。也就是进行数据更新操作。这也就是简单的数据响应式,其实还需要注意的是:当数据的 getter 触发后,会收集依赖,但也不是所有的触发方式都会收集依赖,只有通过 watcher 触发的 getter 会收集依赖:if (Dep.target) {dep.depend() },而所谓的被收集的依赖就是当前 watcher,DOM 中的数据必须通过 watcher 来绑定,只通过 watcher 来读取。
最后付一个函数 timeline 的.png

正文完
 0