对 Vue 观察者及数据劫持的理解

 好久之前增研读过 Vue 源码,感觉精巧优雅,着实敬佩,模块拆分也让人很舒服,但是对于观察者模式的实现以及数据劫持种种原理,了解不甚透彻,近日抽时间再读,某些淤塞的节点上有了一些豁然开朗的感觉,暂时记录下来,以后慢慢详解。

Dep 模块

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
33
34
35
36
37
38
39
40
41
let uid = 0

class Dep {
constructor () {
this.id = uid++
this.subs = [] // 订阅器,存储 Dep.target 指向的 watcher 订阅者
}

addSub (watcher) {
this.subs.push(watcher)
}

removeSub (watcher) {
remove(this.subs, watcher)
}

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

notify () {
const subs = this.subs.slice();
for (let i = 0, l = subs.length; i < l; i++) {
subs[i].update()
}
}
}

Dep.target = null
const targetStack = []

function pushTarget (watcher) {
if (Dep.target) targetStack.push(Dep.target)
Dep.target = watcher
}

function popTarget () {
Dep.target = targetStack.pop()
}

Watcher 模块

Watcher 的实例是订阅者,包括 watchs、computed,创建实例时通过lazy属性来判断是否立即添加自身到订阅器中。

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
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
let uid1 = 0

class Watcher {
constructor (vm, expOrFn, cb, options) {
this.vm = vm
vm._watchers.push(this)

// set porperty by options

this.cb = cb
this.id = ++uid1 // uid for batching
this.active = true
this.dirty = this.lazy // for lazy watchers, it means computed

// set other porperty for dep
this.deps = []
this.newDeps = []
this.depIds = new Set()
this.newDepIds = new Set()

// parse expression for getter
this.getter = expOrFn;

// watchs call get(), get undefined
this.value = this.lazy
? undefined
: this.get()
}

get () {
pushTarget(this) // add watch into TargetStack

let value
let vm = this.vm

// Evaluate the getter
value = this.getter.call(vm, vm)

popTarget() // pop watch out of TargetStack
this.cleanupDeps()

return value
}

addDep (dep) {
const id = dep.id
if (!this.newDepIds.has(id)) {
this.newDepIDs.add(id)
this.newDeps.push(dep)
if (!this.depIds.has(id)) {
dep.addSub(this) // call dep addSub()
}
}
}

cleanupDeps () {
// Clean up for dependency collection
let i = this.deps.length
while (i--) {
const dep = this.deps[i]
if (!this.newDepIds.has(dep.id)) {
dep.removeSub(this) // call dep removeSub()
}
}
let tmp = this.depIds
this.depIds = this.newDepIds
this.newDepIds = tmp
this.newDepIds.clear()
tmp = this.deps
this.deps = this.newDeps
this.newDeps = tmp
this.newDeps.length = 0
}

update () { // be called by dep notify()
if (this.lazy) {
this.dirty = true
} else if (this.sync) {
this.run()
} else {
queueWatcher(this) // watcher queue
}
}

run () {
if (this.active) {
const value = this.get()
if (
value !== this.value ||
// Deep watchers and watchers on Object/Arrays should fire even
// when the value is the same, because the value may
// have mutated.
isObject(value) ||
this.deep
) {
// set new value
const oldValue = this.value
this.value = value
if (this.user) {
try {
this.cb.call(this.vm, value, oldValue)
} catch (e) {
handleError(e, this.vm, `callback for watcher "${this.expression}"`)
}
} else {
this.cb.call(this.vm, value, oldValue)
}
}
}
}

evaluate () {
this.value = this.get()
this.dirty = false
}

depend () { // add this watcher into every dep subs of this.deps
let i = this.deps.length
while (i--) {
this.deps[i].depend() // call dep.Target (the watcher) addDep(dep) to call dep.addSub(watcher)
}
}

}

Observer 模块

Observer 通过 observe 针对对象和数组进行观察者实例化,即在对象或数组上绑定'__ob__'观察者实例对象,该'__ob__'拥有一个依赖实例对象 dep,负责该对象或数组的依赖管理。

针对对象使用 defineReactive 进行数据劫持,该方法内部会创建一个闭包对 Dep 实例化。

  • 订阅:该依赖实例在 get 方法被触发时,会通过 Dep.target 全局变量, 调用 Dep.target 指向的 watcher 实例的 addDep(this) 方法, 判断 dep.id 是否符合条件,如符合则回调 dep.addSub(this), 将watcher自身加入 dep.subs 订阅器中,实现订阅。

  • 通知:该依赖实例在 set 方法被触发时,通过 dep.notify() 通知 dep.subs 中的 watcher 订阅者,执行 watcher.update() 方法

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
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
class Observer {
constructor (value) {
this.value = value
this.dep = new Dep()
this.vmCount = 0 // number of vms that has this object as root $data
def(value, '__ob__', this)

if (Array.isArray(value)) {
this.observeArray(value)
} else {
this.walk(value)
}
}

walk (obj) {
const keys = Object.keys(obj)
for (let i = 0; i < keys.length; i++) {
defineReactive(obj, keys[i], obj[keys[i]])
}
}

observeArray (items) {
for (let i = 0, l = items.length; i < l; i++) {
observe(items[i])
}
}
}

/**
* Attempt to create an observer instance for a value,
* returns the new observer if successfully observed,
* or the existing observer if the value already has one.
*/
function observe (value, asRootData) {
if (!isObject(value) || value instanceof VNode) {
return
}
let ob
if (hasOwn(value, '__ob__') && value.__ob__ instanceof Observer) {
ob = value.__ob__
} else if (
observerState.shouldConvert &&
!isServerRendering() &&
(Array.isArray(value) || isPlainObject(value)) &&
Object.isExtensible(value) &&
!value._isVue
) {
ob = new Observer(value) // for array or object
}
if (asRootData && ob) {
ob.vmCount++
}
return ob
}

/**
* Define a reactive property on an Object.
*/
function defineReactive (obj, key, val, customSetter, shallow
) {
const dep = new Dep()

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

// cater for pre-defined getter/setters
const getter = property && property.get
const setter = property && property.set

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.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
/* eslint-disable no-self-compare */
if (newVal === value || (newVal !== newVal && value !== value)) {
return
}
/* eslint-enable no-self-compare */
if (process.env.NODE_ENV !== 'production' && customSetter) {
customSetter()
}
if (setter) {
setter.call(obj, newVal)
} else {
val = newVal
}
childOb = !shallow && observe(newVal)
dep.notify()
}
})
}

三个模块的关系及应用

  • Vue 构造函数,源码入口
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
function Vue (options) {
if (process.env.NODE_ENV !== 'production' &&
!(this instanceof Vue)
) {
warn('Vue is a constructor and should be called with the `new` keyword')
}
this._init(options)
}

// 将 Vue 传入各初始化函数对 Vue.prototype 进行初始化
initMixin(Vue)
stateMixin(Vue)
eventsMixin(Vue)
lifecycleMixin(Vue)
renderMixin(Vue)
  • 在 initMixin 中给 Vue.prototype 赋值 _init 函数
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
// _init 会调用各初始化函数
function initMixin (Vue) {
Vue.prototype._init = function (options) {
{
initProxy(vm);
}
// expose real self
vm._self = vm;
initLifecycle(vm);
initEvents(vm);
initRender(vm);
callHook(vm, 'beforeCreate');
initInjections(vm); // resolve injections before data/props
initState(vm);
initProvide(vm); // resolve provide after data/props
callHook(vm, 'created');

if (vm.$options.el) {
vm.$mount(vm.$options.el);
}
}
}
  • initRender、initInjections 会调用 defineReactive 进行数据劫持
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
function initRender (vm) {
vm._c = function (a, b, c, d) { return createElement(vm, a, b, c, d, false); };
// normalization is always applied for the public version, used in
// user-written render functions.
vm.$createElement = function (a, b, c, d) { return createElement(vm, a, b, c, d, true); };

// $attrs & $listeners are exposed for easier HOC creation.
// they need to be reactive so that HOCs using them are always updated
var parentData = parentVnode && parentVnode.data;

/* istanbul ignore else */
{
defineReactive(vm, '$attrs', parentData && parentData.attrs || emptyObject, function () {
!isUpdatingChildComponent && warn("$attrs is readonly.", vm);
}, true);
defineReactive(vm, '$listeners', options._parentListeners || emptyObject, function () {
!isUpdatingChildComponent && warn("$listeners is readonly.", vm);
}, true);
}
}
  • initState 调用 initProps,通过 defineReactive 数据劫持;调用 initData,通过 observe 函数创建 Observer 实例;然后调用 initWatch、initComputed 创建 Watcher 实例,将自己加入依赖对象的订阅数组中。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
function initState (vm) {
vm._watchers = [];
var opts = vm.$options;
if (opts.props) { initProps(vm, opts.props); }
if (opts.methods) { initMethods(vm, opts.methods); }
if (opts.data) {
initData(vm);
} else {
observe(vm._data = {}, true /* asRootData */);
}
if (opts.computed) { initComputed(vm, opts.computed); }
if (opts.watch && opts.watch !== nativeWatch) {
initWatch(vm, opts.watch);
}
}

function initData (vm) {
// init data logic ...
// observe data
observe(data, true /* asRootData */);
}
  • observe 函数调用 Observer 并创建实例过程中会通过 walk 函数遍历调用 defineReactive 进行数据劫持, 若传入构造函数值为数据则用 observe 递归
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
function observe () {
// if logic...
ob = new Observer(value)
return ob
}

class Observer {
constructor (value) {
this.value = value
this.dep = new Dep()
this.vmCount = 0

def(value, '__ob__', this)

if (Array.isArray(value)) {
// handle augment logic...

this.observeArray(value)
} else {
this.walk(value)
}
}

walk (obj) {
const keys = Object.keys(obj)
for (let i = 0; i < keys.length; i++) {
defineReactive(obj, keys[i], obj[keys[i]])
}
}

observeArray (items) {
for (let i = 0, l = items.length; i < l; i++) {
observe(items[i])
}
}
}
  • defineReactive 是通过 Object.defineProperty 进行数据劫持的核心函数,Watcher、Dep、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
43
44
45
46
47
function defineReactive (obj, key, val, customSetter, shallow) {
const dep = new Dep()

const property = Object.getOwnPropertyDescriptor(obj, key)

// cater for pre-defined getter/setters
const getter = property && property.get
const setter = property && property.set

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.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
/* eslint-disable no-self-compare */
if (newVal === value || (newVal !== newVal && value !== value)) {
return
}
/* eslint-enable no-self-compare */
if (process.env.NODE_ENV !== 'production' && customSetter) {
customSetter()
}
if (setter) {
setter.call(obj, newVal)
} else {
val = newVal
}
childOb = !shallow && observe(newVal)
dep.notify()
}
})
}

数组方法变异

  • 数组方法变异其实就是改写那些会改变原数组的原型方法( 'push', 'pop', 'shift', 'unshift', 'splice', 'sort', 'reverse'),具体有两步骤。

    • 针对可以增加项目的方法('push', 'unshift', 'splice')将其增加的参数args遍历进行observe,
    • 在方法返回值前,用 this.__ob__ (该数组的观察实例对象) 调用依赖 this.__ob__.dep.notify() 通知变更。
  • 针对数组新增、或更改项目,提供 set 方法调用 dep.notify() 通知变更,新增将调用 defineReactive 进行订阅依赖。

  • 针对数组项目删除,提供 del 方法调用 dep.notify() 通知变更
  • set、del 方法会绑定 Vue 的全局变量 set、delete 和 Vue.prototype 的 $set、$delete 上。