通过object.defineProperty设置getter和setter
vue初始化调用_init方法, 在init中会执行initState方法,对props,methods,data等属性进行初始化, 主要代码如下:
1 | // 省略部分代码 |
其中主要方法作用如下:
- initProps()调用deineReactive方法使其变成响应式。
- initData()->调用observe方法使其变为响应式
- observe()给对象添加getter和setter,用于依赖收集和派发更新。代码如下:主要是实例化Observer实现响应式, Observer的实现如下: 通过定义getter和setter实现
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21export function observe (value: any, asRootData: ?boolean): Observer | void {
if (!isObject(value) || value instanceof VNode) {
return
}
let ob: Observer | void
if (hasOwn(value, '__ob__') && value.__ob__ instanceof Observer) {
ob = value.__ob__
} else if (
shouldObserve &&
!isServerRendering() &&
(Array.isArray(value) || isPlainObject(value)) &&
Object.isExtensible(value) &&
!value._isVue
) {
ob = new Observer(value)
}
if (asRootData && ob) {
ob.vmCount++
}
return ob
}definReactive定义一个响应式对象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
35export class Observer {
value: any;
dep: Dep;
vmCount: number;
constructor (value: any) {
this.value = value
// 实例化Dep对象,Dep是一个订阅者,用来存放Watcher观察者对象
this.dep = new Dep()
this.vmCount = 0
// 添加 this.__ob__ = value
def(value, '__ob__', this)
if (Array.isArray(value)) {
const augment = hasProto
? protoAugment
: copyAugment
augment(value, arrayMethods, arrayKeys)
this.observeArray(value)
} else {
this.walk(value)
}
}
// 遍历对象的 key 调用 defineReactive
walk (obj: Object) {
const keys = Object.keys(obj)
for (let i = 0; i < keys.length; i++) {
defineReactive(obj, keys[i])
}
}
// 遍历数组调用observe
observeArray (items: Array<any>) {
for (let i = 0, l = items.length; i < l; i++) {
observe(items[i])
}
}
}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
59export function defineReactive (
obj: Object,
key: string,
val: any,
customSetter?: ?Function,
shallow?: boolean
) {
// 实例化Dep对象
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
if ((!getter || setter) && arguments.length === 2) {
val = obj[key]
}
// 对子对象递归调用
let childOb = !shallow && observe(val)
//给对象添加getter和setter
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()
}
})
}依赖收集和派发更新
依赖收集
- defineReactive的get方法会调用dep.depen()进行依赖收集,Dep类的主要作用是管理Watch对象
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
33export default class Dep {
// target全局唯一 Watcher
static target: ?Watcher;
id: number;
subs: Array<Watcher>;
constructor () {
this.id = uid++
this.subs = []
}
// 在 subs中添加Watcher,也就是添加属性发生改变时,要通知的watcher
addSub (sub: Watcher) {
this.subs.push(sub)
}
// 移除Wathcer
removeSub (sub: Watcher) {
remove(this.subs, sub)
}
// 依赖收集
depend () {
if (Dep.target) {
Dep.target.addDep(this)
}
}
// 通知Watcher更新,也就是派发更新
notify () {
// stabilize the subscriber list first
const subs = this.subs.slice()
for (let i = 0, l = subs.length; i < l; i++) {
subs[i].update()
}
}
} - Watcher类的定义如下:
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
121let uid = 0
export default class Watcher {
constructor (
vm: Component,
expOrFn: string | Function,
cb: Function,
options?: ?Object,
isRenderWatcher?: boolean
) {
this.vm = vm
if (isRenderWatcher) {
vm._watcher = this
}
vm._watchers.push(this)
// options
if (options) {
this.deep = !!options.deep
this.user = !!options.user
this.computed = !!options.computed
this.sync = !!options.sync
this.before = options.before
} else {
this.deep = this.user = this.computed = this.sync = false
}
this.cb = cb
this.id = ++uid // uid for batching
this.active = true
this.dirty = this.computed // for computed watchers
this.deps = []
this.newDeps = []
this.depIds = new Set()
this.newDepIds = new Set()
this.expression = process.env.NODE_ENV !== 'production'
? expOrFn.toString()
: ''
// parse expression for getter
if (typeof expOrFn === 'function') {
this.getter = expOrFn
} else {
this.getter = parsePath(expOrFn)
if (!this.getter) {
this.getter = function () {}
process.env.NODE_ENV !== 'production' && warn(
`Failed watching path: "${expOrFn}" ` +
'Watcher only accepts simple dot-delimited paths. ' +
'For full control, use a function instead.',
vm
)
}
}
if (this.computed) {
this.value = undefined
this.dep = new Dep()
} else {
this.value = this.get()
}
}
/**
* Evaluate the getter, and re-collect dependencies.
*/
get () {
pushTarget(this)
let value
const vm = this.vm
try {
value = this.getter.call(vm, vm)
} catch (e) {
if (this.user) {
handleError(e, vm, `getter for watcher "${this.expression}"`)
} else {
throw e
}
} finally {
// "touch" every property so they are all tracked as
// dependencies for deep watching
if (this.deep) {
traverse(value)
}
popTarget()
this.cleanupDeps()
}
return value
}
/**
* Add a dependency to this directive.
*/
addDep (dep: 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)
}
}
}
/**
* Clean up for dependency collection.
*/
cleanupDeps () {
let i = this.deps.length
while (i--) {
const dep = this.deps[i]
if (!this.newDepIds.has(dep.id)) {
dep.removeSub(this)
}
}
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
}
// ...
}1
2
3this.deps = []
this.newDeps = []
//表示Wather实例持有的Dep实例,也就是订阅了多少dep
- 组件挂载时会调用mountComponent函数,在mountComponent中实例化一个渲染Wathcer,而在实例化Wathcer时调用this.get()
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17get () {
pushTarget(this)
let value
const vm = this.vm
try {
value = this.getter.call(vm, vm)
} catch (e) {
// ....
} finally {
if (this.deep) {
traverse(value)
}
popTarget()
this.cleanupDeps()
}
return value
} - 调用pushTarget(this)将要挂载的组件渲染Wather对象赋值给Dep.target -> 执行this.getter.call(vm,vm),实际上是执行new Wathcer时传入的回调函数->执行updateComponent()
1
new Watcher(vm, updateComponent, //...)
- updateComponent有对组件data数据的访问-> 触发defineReactive的get方法 -> 依赖收集dep.depen()->调用Dep.target.addDep(this)->实际是调用渲染Watcher实例中的addDep
1
2
3
4
5
6
7
8
9
10
11addDep (dep: 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)
}
}
} - 将这个渲染Wather添加到在defineReactive中实例化的dep中的subs中,这就是整个依赖收集过程。
派发更新
- set时,派发更新的主要流程如下:
1
2
3
4
5
6
7
8
9
10
11set: function reactiveSetter (newVal) {
const value = getter ? getter.call(obj) : val
// ....
if (setter) {
setter.call(obj, newVal)
} else {
val = newVal
}
childOb = !shallow && observe(newVal)
dep.notify()
} - observe(newVal)使新设置的值变成响应式的
- dep.notify()通知所有订阅了这个dep的Wathcher对象-> dep.notify()调用Watcher的update()方法
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15update () {
if (this.computed) {
if (this.dep.subs.length === 0) {
this.dirty = true
} else {
this.getAndInvoke(() => {
this.dep.notify()
})
}
} else if (this.sync) {
this.run()
} else {
queueWatcher(this)
}
} - 一般组件数据更新会调用queueWatcher(this)
- queueWathcer使用了队列,先把Wather添加到队列queue,在下个nextTick执行flushSchedulerQueue() -> 执行watcher.run()方法
1
2
3
4
5run () {
if (this.active) {
this.getAndInvoke(this.cb)
}
} - run()方法执行->getAndInvoke(this.cb)传入回调函数->getAndInvoke判断满足新旧值不等->执行this.cb回调函数
- 在渲染watcher中,this.cb为所以当修改组件相关的响应式数据时会触发组件重新渲染
1
2
3updateComponent = () => {
vm._update(vm._render(), hydrating)
}
computed实现
- Vue实例初始化时调用initComputed()
在initComputed()中为每一个计算属性getter创建computed Wather,computed Wathcer初始化时会持有一个dep实例,用来管理computed Wathcer. 当update()时,要更新订阅了这个dep的watcher。1
2
3
4if (this.computed) {
this.value = undefined
this.dep = new Dep()
} - 最后调用defineComputed()
defineComputed()–>调用Object.defineProperty为计算属性设置对应的key的getter和setter,一般setter为空,getter对应createComputedGetter(key)当render 函数访问到计算属性时-> 触发getter-> 执行watcher.depend->将渲染Wather订阅上文实例化的dep->执行watcher.evaluate()1
2
3
4
5
6
7
8
9function createComputedGetter (key) {
return function computedGetter () {
const watcher = this._computedWatchers && this._computedWatchers[key]
if (watcher) {
watcher.depend()
return watcher.evaluate()
}
}
}watcher.evaluate()执行this.get()-> this.get()为计算属性定义的getter函数获取到this.value值1
2
3
4
5
6
7evaluate () {
if (this.dirty) {
this.value = this.get()
this.dirty = false
}
return this.value
}
此时也会访问到计算属性所依赖的数据的getter,所以Dep.target为这个computed watcher,此时这个computed watcher订阅了所依赖数据持有的dep
当对计算属性依赖的数据做修改时->触发数据setter->通知订阅它变化的computed watcher更新-> 执行watcher.update()方法
1 | if (this.computed) { |
在上文中渲染Wather订阅了this.dep, 所以渲染Wathcer通过this.getAndInvoke()的回调执行update()->重新渲染组件
this.getAndInvoke()主要是对比新旧值
在computed Watcher中this.dirty的作用时在重新渲染组件后,通过evalute()获取计算属性的值时,因为在getAndInvoke()中将this.value设为新值并置this.dirty为false,所以直接返回this.value,不触发所依赖数据的getter
1 | // 在 getAndInvoke()中 |
watch的实现
Vue实例初始化时->initWatch()->调用createWatcher(vm, key, handler[i])->返回vm.$watch(expOrFn, handler, options)
$watch在执行stateMixin时定义
实例化了Wathcer
1 | const watcher = new Watcher(vm, expOrFn, cb, options) |
是一个 user Wathcer, options.user = true,
在实例化 user Wathcer时,调用了watcher的this.get()方法,之后的依赖收集和更新与一般组件数据相同,不同是对options属性值不同时,进入不同的分支.