聊聊Vue的双向数据绑定

先读一段对话娱乐一下~

我:——你在干嘛?

男票(Android客户端):——吃屎

我:——emmmm,懂了(他在读Android源码···)

好吧~我也要直播吃屎了······接下来就是我看完vue/src/core/observer之后的总结以及源码分析~看源码不仅是看实现原理,也是看大牛的编码风格,以及为日后接触别的框架源码打基础,今天我主要聊的就是Vue的双向数据绑定~


Vue在双向数据绑定这块做的事情就是:

  • 通过Observer(数据监听器)对data中的属性做监听,为data中的每一个属性提供Dep(消息订阅器),Dep内部维护了一个数组subs用来添加Watcher(订阅者)

  • 把template编译成一段document fragment,然后解析其中的Directive(指令),得到每一个Directive所依赖的数据项和update方法,并为每一个与数据绑定的节点生成一个Watcher,Watcher会将自己添加到对应属性的Dep中

  • 通过Watcher将Observer和template的Directive连接起来。template通过一次render(渲染)操作触发所有被render所依赖(保证只有视图中需要被用到的data才会触发getter)的data中的数据的getter进行依赖收集(通过Watcher订阅在对应数据的Observer的Dep的subs中);当数据发生变化的时候,就会触发数据的setter,setter会触发Observer的Dep调用notify方法来通知对应的Watcher进行通知组件重新渲染的update方法,进而触发Directive的update方法来更新视图

从vue如何给data添加Observer开始,回顾一下vue实例创建的生命周期,其中,vm.initData方法处理data选项。

initData:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
// 源码目录:src/core/instance/state.js
function initData (vm: Component) {
let data = vm.$options.data
data = vm._data = typeof data === 'function'
? getData(data, vm)
: data || {}
if (!isPlainObject(data)) {
data = {}
process.env.NODE_ENV !== 'production' && warn()
}
// proxy data on instance
const keys = Object.keys(data)
const props = vm.$options.props
const methods = vm.$options.methods
let i = keys.length
while (i--) {
const key = keys[i]
if (process.env.NODE_ENV !== 'production') {
if (methods && hasOwn(methods, key)) {
warn()
}
}
if (props && hasOwn(props, key)) {
process.env.NODE_ENV !== 'production' && warn()
} else if (!isReserved(key)) {
proxy(vm, `_data`, key)
}
}
// observe data
observe(data, true /* asRootData */)
}

这段代码的重点其实在proxy和observer。可以看到proxy的功能就是遍历data的key,把data上的属性代理到vm实例上。observer的功能就是对data作监听了。

proxy:

1
2
3
4
5
6
7
8
9
10
11
源码目录:src/core/instance/state.js
export function proxy (target: Object, sourceKey: string, key: string) {
sharedPropertyDefinition.get = function proxyGetter () {
return this[sourceKey][key]
}
sharedPropertyDefinition.set = function proxySetter (val) {
this[sourceKey][key] = val
}
Object.defineProperty(target, key, sharedPropertyDefinition) //
proxy方法通过Object.defineProperty()的getter和setter方法实现了对data的每个key的代理,假如我们调用vm.count就相当于访问vm.data.count。
}

observe:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
源码目录:src/core/observer/index.js
export function observe (value: any, asRootData: ?boolean): Observer | void {
let ob: Observer | void
// __ob__属性是Observer的实例,所以首先判断value是否已经添加了ob属性,如果没有则会新建一个Observer对象并给它赋值__ob__,如果有,则直接将value的__ob__的属性赋值给ob变量。最终返回ob
if (hasOwn(value, '__ob__') && value.__ob__ instanceof Observer) {
ob = value.__ob__
} else if (
// 确保value是对象,而不是数组或者正则等情况,还有避免对属性重新进行observer
shouldObserve &&
!isServerRendering() &&
(Array.isArray(value) || isPlainObject(value)) &&
Object.isExtensible(value) &&
!value._isVue
) {
ob = new Observer(value)
}
if (asRootData && ob) {
ob.vmCount++
}
return ob
}

Observer类:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
源码目录:src/core/observer/index.js
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() // 创建一个Dep(消息订阅器)的实例
this.vmCount = 0
def(value, '__ob__', this) // def方法定义在util/lang.js下,用来把自身this添加到data中value的__ob__属性上去

// 接着判断观察者实例data中的属性值是否为数组,如果是数组并且浏览器支持__proto__,则直接覆盖数组的原型方法来修改目标数组,如果浏览器不支持__prpto__,则通过定义目标数组的某一个方法来实现修改后可以截取响应的数组方法监听数据变化响应的效果
if (Array.isArray(value)) {
const augment = hasProto
? protoAugment // 直接覆盖原型方法来修改目标对象
: copyAugment // 定义(覆盖)目标对象或数组的某一个方法
augment(value, arrayMethods, arrayKeys)

// 如果是数组的话则需要遍历数组,递归调用oberser
this.observeArray(value)
} else {
// 如果不是数组是对象的话,直接调用walk方法进行绑定
this.walk(value)
}
}

// Walk方法是仅当data中的属性值为对象的时候,给每个属性绑定getter/setter
walk (obj: Object) {
const keys = Object.keys(obj)
for (let i = 0; i < keys.length; i++) {
defineReactive(obj, keys[i]) // 遍历对象上的每一个属性依次调用defineReactive
}
}

// 对数组的属性列表进行绑定
observeArray (items: Array<any>) {
for (let i = 0, l = items.length; i < l; i++) {
observe(items[i])
}
}
}

defineReactive:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
// 源码目录:src/core/observer/index.js
export function defineReactive (
obj: Object,
key: string,
val: any,
customSetter?: ?Function,
shallow?: boolean
) {
const dep = new Dep() // 在闭包中定义dep

const property = Object.getOwnPropertyDescriptor(obj, key)
if (property && property.configurable === false) {
return
}

// 保证之前已经预定义了的getter/setter不被覆盖
// cater for pre-defined getter/setters
const getter = property && property.get
const setter = property && property.set
if ((!getter || setter) && arguments.length === 2) {
val = obj[key]
}

let childOb = !shallow && observe(val)
Object.defineProperty(obj, key, {
enumerable: true,
configurable: true,
get: function reactiveGetter () {
const value = getter ? getter.call(obj) : val
if (Dep.target) {
// 当Dep.target不为空时调用dep.depend()和childOb.dep.depend()进行依赖收集

dep.depend()
if (childOb) {
childOb.dep.depend()
if (Array.isArray(value)) {
// 如果访问的是个数组则需要对数组的每一个元素都进行依赖收集,如果数组的成员还是数组则递归
dependArray(value)
}
}
}
return value
},
set: function reactiveSetter (newVal) {
const value = getter ? getter.call(obj) : val
if (setter) {
// 如果对象原本就有setter,则执行绑定
setter.call(obj, newVal)
} else {
val = newVal
}
// 当改变data的属性的时候,新的值需要重新进行监测,保证数据的响应式,所以要调用setter方法,再调用dep.notify方法进行通知
childOb = !shallow && observe(newVal)
dep.notify()
}
})
}

Dep:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
// 源码目录:src/core/observer/dep.js
export default class Dep {
static target: ?Watcher;
id: number;
subs: Array<Watcher>;

constructor () { // 初始化id和subs,其中subs用来存储所有的Watcher(订阅者)。
this.id = uid++
this.subs = []
}

addSub (sub: Watcher) { // 添加一个观察对象
this.subs.push(sub)
}

removeSub (sub: Watcher) { // 移除一个观察对象
remove(this.subs, sub)
}

depend () { // 上面的get方法中调用了depend方法
if (Dep.target) { // Dep.target表示正在计算的Watcher,是全局唯一的,因为同一时间只能有一个Watcher被计算。
Dep.target.addDep(this) // 将当前的Dep实例添加到正在计算的Watcher的依赖中
}
}

notify () { // 上面的set方法中调用了notify方法
const subs = this.subs.slice()
for (let i = 0, l = subs.length; i < l; i++) {
subs[i].update() // 遍历所有的订阅Watcher,调用他们的update方法
}
}
}

依赖收集:Dep类是一个简单的观察者模式的实现。当对data上的对象进行修改值的时候会触发它的setter,那么取值则会触发它的getter。所以,将Watcher实例赋值给全局的Dep.target,然后触发render操作,只有被被Dep.target标记过的data中的数据才会被getter收集到Dep的subs中;在对data中的数据进行修改的时候,触发setter的时候,Dep会调用subs中的Watcher实例的update方法进行渲染。