响应式原理-2.x版本(下)

本文最后更新于:2020年1月21日 上午

上一篇分析了data的初始化过程以及做的一些操作,这一篇分析依赖收集派发更新

依赖收集

先看下getter的代码

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
export function defineReactive (
obj: Object,
key: string,
val: any,
customSetter?: ?Function,
shallow?: boolean
) {
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)
Object.defineProperty(obj, key, {
enumerable: true,
configurable: true,
// 重点看get这部分
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
},
// ...
})
}

getter的逻辑里面,关键的两步是const dep = new Dep()dep.depend(),第一步是实例化一个Dep实例,第二步就是实现依赖收集。

先看第一步,Dep的实现,代码路径/src/core/observer/dep.js

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
42
43
44
45
46
47
48
49
50
51
52
import type Watcher from './watcher'
import { remove } from '../util/index'

let uid = 0

/**
* A dep is an observable that can have multiple
* directives subscribing to it.
*/
export default 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)
}
// 依赖收集 存在target时添加观察者对象
depend () {
if (Dep.target) {
Dep.target.addDep(this)
}
}
// 派发更新 省略 下面有用到
notify () {
// ...
}
}

// the current target watcher being evaluated.
// this is globally unique because there could be only one
// watcher being evaluated at any time.
Dep.target = null
const targetStack = []

export function pushTarget (_target: ?Watcher) {
if (Dep.target) targetStack.push(Dep.target)
Dep.target = _target
}

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

可以看到,Dep是对subs数组的操作,而subswatcher数组,所以Dep其实是对watchers的管理。

接着看watcher的逻辑,代码路径/src/core/observer/watcher.js

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
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
let uid = 0
/**
* A watcher parses an expression, collects dependencies,
* and fires callback when the expression value changes.
* This is used for both the $watch() api and directives.
*/
export default class Watcher {
deps: Array<Dep>;
newDeps: Array<Dep>;
// 此处省略一堆类型声明, 具体在源码查看
// ...

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 () {
// 入栈 缓存Watcher
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) {
// 递归对象或数组 触发getter
traverse(value)
}
// 出栈 恢复Watcher
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
}
// 后面省略
// ...
}

从代码可以看到,实例化一个Watcher时,会执行get函数,通过pushTarget(this)将当前Watcher入栈,然后又执行this.getter.call(vm, vm),这里的getter对应了updateComponent 函数,这里会执行一句关键代码vm._update(vm._render(), hydrating)vm._render()会访问vm上面的数据,这里就会触发所有数据的getter,完成依赖收集。

触发依赖收集之后,根据上面代码里的注释,vue还做了三步操作

  1. 如果存在deep属性,说明存在深度依赖关系,采用递归进行依赖收集
  2. 执行popTarget()出栈,恢复watcher成上一状态
  3. 完成新订阅,执行cleanupDeps(),清除旧订阅,也就是清除旧依赖

派发更新

看下setter代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
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()
}
// #7981: for accessor properties without setter
if (getter && !setter) return
if (setter) {
setter.call(obj, newVal)
} else {
val = newVal
}
// shallow为false时 将新值设置成响应式对象
childOb = !shallow && observe(newVal)
// 通知订阅者
dep.notify()
}

值发生变化时,会调用depnotify方法,看下Depnotify的定义

1
2
3
4
5
6
7
8
// 派发更新
notify () {
// stabilize the subscriber list first
const subs = this.subs.slice()
for (let i = 0, l = subs.length; i < l; i++) {
subs[i].update()
}
}

这里调用了每个watcherupdate方法

1
2
3
4
5
6
7
8
9
10
11
12
update () {
/* istanbul ignore else */
if (this.lazy) {
this.dirty = true
// 同步时执行
} else if (this.sync) {
this.run()
// 推送到观察者队列
} else {
queueWatcher(this)
}
}

这里关注runqueueWatcher

run

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
// run 定义在watcher里面
/**
* Scheduler job interface.
* Will be called by the scheduler.
*/
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)
}
}
}
}

run的作用就是拿到当前的值,进行新旧值相等、是否对象、是否有deep等判断,满足其中之一就进行回调,而在通过this.get()拿值的时候,就会触发执行getter,这也是响应式数据修改时触发渲染的原因。

queueWatcher

代码路径 src/core/observer/scheduler.js

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
const queue: Array<Watcher> = []
const activatedChildren: Array<Component> = []
let has: { [key: number]: ?true } = {}
let circular: { [key: number]: number } = {}
let waiting = false
let flushing = false
let index = 0

/**
* Push a watcher into the watcher queue.
* Jobs with duplicate IDs will be skipped unless it's
* pushed when the queue is being flushed.
*/
export function queueWatcher (watcher: Watcher) {
const id = watcher.id
// 保证每个watcher只会被添加一次
if (has[id] == null) {
has[id] = true
if (!flushing) {
queue.push(watcher)
} else {
// if already flushing, splice the watcher based on its id
// if already past its id, it will be run next immediately.
let i = queue.length - 1
while (i > index && queue[i].id > watcher.id) {
i--
}
queue.splice(i + 1, 0, watcher)
}
// queue the flush
if (!waiting) {
waiting = true
// 同步
if (process.env.NODE_ENV !== 'production' && !config.async) {
flushSchedulerQueue()
return
}
// 在下一个tick 异步执行flushSchedulerQueue
nextTick(flushSchedulerQueue)
}
}
}

vue在派发更新的时候使用了队列,这样做的好处就是每次数据改变时不会马上触发watcher的回调,会先将watcher添加到队列里,然后在nextTick后执行flushSchedulerQueue

flushSchedulerQueue

代码路径 src/core/observer/scheduler.js

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
/**
* Flush both queues and run the watchers.
*/
function flushSchedulerQueue () {
currentFlushTimestamp = getNow()
flushing = true
let watcher, id

// Sort queue before flush.
// This ensures that:
// 1. Components are updated from parent to child. (because parent is always
// created before the child)
// 2. A component's user watchers are run before its render watcher (because
// user watchers are created before the render watcher)
// 3. If a component is destroyed during a parent component's watcher run,
// its watchers can be skipped.
// 队列排序 原因如上注释
queue.sort((a, b) => a.id - b.id)

// do not cache length because more watchers might be pushed
// as we run existing watchers
// 队列遍历
for (index = 0; index < queue.length; index++) {
watcher = queue[index]
if (watcher.before) {
watcher.before()
}
id = watcher.id
has[id] = null
watcher.run()
// in dev build, check and stop circular updates.
if (process.env.NODE_ENV !== 'production' && has[id] != null) {
circular[id] = (circular[id] || 0) + 1
if (circular[id] > MAX_UPDATE_COUNT) {
warn(
'You may have an infinite update loop ' + (
watcher.user
? `in watcher with expression "${watcher.expression}"`
: `in a component render function.`
),
watcher.vm
)
break
}
}
}

// keep copies of post queues before resetting state
const activatedQueue = activatedChildren.slice()
const updatedQueue = queue.slice()

resetSchedulerState()

// call component updated and activated hooks
callActivatedHooks(activatedQueue)
callUpdatedHooks(updatedQueue)

// devtool hook
/* istanbul ignore if */
if (devtools && config.devtools) {
devtools.emit('flush')
}
}

flushSchedulerQueue在这里做了两件事,队列排序队列遍历

队列排序的原因,源码上面也已经有了详细的注释,这里不再赘述。

然后对排序后的队列进行遍历,拿到watcher执行run方法,run已在上面分析。

结语

通过上一篇和这一篇的分析,再加上vue源码本身的注释,基本是理清了响应式2.x版本的过程和原理,那vue3.0使用了proxy来替代,原理理解得差不多,理解新的实现也不会太麻烦,后面就期待3.0的到来吧~


本博客所有文章除特别声明外,均采用 CC BY-SA 4.0 协议 ,转载请注明出处!