Vue响应式之data
# 前言
上一篇我们介绍了 vue 响应式的处理流程,并实现了一个简单的响应式框架
本文就从 data 源码入手,深扒那些被人忽视的处理细节
# 源码解析
根据 Vue实例化过程 一文,我们知道 data 的处理从 src\core\instance\state.js
的 initData 开始
//src\core\instance\state.js
function initData (vm: Component) {
let data = vm.$options.data
// ...省略类型判断
// ...省略 与props和methods的同名判断
// observe data
observe(data, true /* asRootData */)
}
2
3
4
5
6
7
8
9
最后调用了 observe 方法
// src\core\observer\index.js
export 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
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
重点在于会创建 value(此处为data) 的观察者实例 ob
进入 Observer 实例化处理
// src\core\observer\index.js
export class Observer {
value: any;
dep: Dep;
vmCount: number; // number of vms that have this object as root $data
constructor (value: any) {
this.value = value
this.dep = new Dep()
this.vmCount = 0
// 在对象上定义一个 __ob__ 属性并指向该观察者
def(value, '__ob__', this)
if (Array.isArray(value)) {
if (hasProto) {
protoAugment(value, arrayMethods)
} else {
copyAugment(value, arrayMethods, arrayKeys)
}
this.observeArray(value)
} else {
this.walk(value)
}
}
/**
* 遍历所有属性并将它们转换为 getter / setter
*/
walk (obj: Object) {
const keys = Object.keys(obj)
for (let i = 0; i < keys.length; i++) {
defineReactive(obj, keys[i])
}
}
/**
* 观察数组中的每一项
*/
observeArray (items: Array<any>) {
for (let i = 0, l = items.length; i < l; i++) {
observe(items[i])
}
}
}
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
Observer 类会附加到每一个被观察对象。附加后,观察者会转换目标对象的属性,通过 getter/setters 来收集依赖和调度更新
我们先以非数组对象为例,此时会进入 walk 方法,每个属性调用 defineReactive
// src\core\observer\index.js
/**
* 在 obj 上定义响应式属性
*/
export function defineReactive (
obj: Object,
key: string,
val: any,
customSetter?: ?Function,
shallow?: boolean
) {
// 定义一个依赖收集
const dep = new Dep()
// 属性设置不可配置即返回
// 常见的如 Object.freeze(obj), obj 的属性描述将变为 configurable === false
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
// 若未定义 getter 或存在 setter 需要设置 val 的值,后续会用到
if ((!getter || setter) && arguments.length === 2) {
val = obj[key]
}
// 递归观察 val 的子元素
let childOb = !shallow && observe(val)
Object.defineProperty(obj, key, {
enumerable: true,
configurable: true,
get: function reactiveGetter () {
// 定义 getter 则采用 getter 取值,否则使用闭包变量 val
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 */
// 前后数组不变不进行处理
// newVal !== newVal && value !== value 用于处理 NaN
if (newVal === value || (newVal !== newVal && value !== value)) {
return
}
/* eslint-enable no-self-compare */
// 暂未找到设置 customSetter 的入口,略
if (process.env.NODE_ENV !== 'production' && customSetter) {
customSetter()
}
// 没有 setter 的访问器属性直接 return
if (getter && !setter) return
if (setter) {
setter.call(obj, newVal)
} else {
val = newVal
}
// 需要对新的子元素重新进行观察
childOb = !shallow && observe(newVal)
// 通知依赖处理变更
dep.notify()
}
})
}
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
Dep 就是一个收集依赖的,上一篇文章 Vue响应式处理 已经介绍过它的作用了,我们稍后分析,先看看这里有哪些处理细节
- 不可配置属性不进行响应式处理
if (property && property.configurable === false) {
return
}
2
3
由于 configurable 为 false ,不能对该属性使用 Object.defineProperty ,因此这里不进行响应式处理
- 数据属性的处理
当属性初始为数据属性时,我们采用 val 闭包变量来进行数据的设置和获取
一开始设置初始值
if ((!getter || setter) && arguments.length === 2) {
val = obj[key]
}
2
3
get 方法中返回 val
set 方法中对 val 进行赋值
- 访问器属性的处理
当该属性的初始 Descriptor 存在 get 或 set, 则该属性为访问器属性
用 getter 和 setter 保存初始描述的 get 和 set
此时存在三种情况:
- getter/setter 同时存在
此时处理同数据属性类似,不过这里是 get 中用 getter.call(obj)
返回值, set 中用 setter.call(obj, newVal)
设置值,不需要用到闭包变量 val
- getter 不存在,setter 存在
由于没有 getter 方法,在 get 方法中会一直返回 undefined ,在 set 中虽然设置值没有效果,但还是会通知依赖处理变更
暂时不知道适用哪些场景
- getter 存在,setter 不存在
在 get 中会返回 getter.call(obj)
,在 set 中不进行处理(包括通知依赖处理变更)
暂时不知道适用哪些场景
现在继续分析 Dep 这个依赖处理模块
// src\core\observer\dep.js
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)
}
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 = []
export function pushTarget (target: ?Watcher) {
targetStack.push(target)
Dep.target = target
}
export function popTarget () {
targetStack.pop()
Dep.target = targetStack[targetStack.length - 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
收集依赖,观察目标改变时通知观察者更新,和我们上篇文章的处理大致一样
看下 Watcher 是怎么定义的
// src\core\observer\watcher.js
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.lazy = !!options.lazy
this.sync = !!options.sync
this.before = options.before
} else {
this.deep = this.user = this.lazy = this.sync = false
}
this.cb = cb
this.id = ++uid // uid for batching
this.active = true
this.dirty = this.lazy // for lazy 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)
// ...省略异常提示
}
// lazy 为 false 时,调用 get 进行依赖绑定
this.value = this.lazy
? undefined
: this.get()
}
/**
* Evaluate the getter, and re-collect dependencies.
*/
get () {
pushTarget(this)
let value
const vm = this.vm
try {
// 实现依赖和观察者绑定 -- addDep
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
}
/**
* Subscriber interface.
* Will be called when a dependency changes.
*/
update () {
/* istanbul ignore else */
if (this.lazy) {
this.dirty = true
} else if (this.sync) {
this.run()
} else {
queueWatcher(this)
}
}
/**
* 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)
}
}
}
}
/**
* Evaluate the value of the watcher.
* This only gets called for lazy watchers.
*/
evaluate () {
this.value = this.get()
this.dirty = false
}
/**
* Depend on all deps collected by this watcher.
*/
depend () {
let i = this.deps.length
while (i--) {
this.deps[i].depend()
}
}
/**
* Remove self from all dependencies' subscriber list.
*/
teardown () {
if (this.active) {
// remove self from vm's watcher list
// this is a somewhat expensive operation so we skip it
// if the vm is being destroyed.
if (!this.vm._isBeingDestroyed) {
remove(this.vm._watchers, this)
}
let i = this.deps.length
while (i--) {
this.deps[i].removeSub(this)
}
this.active = false
}
}
}
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
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
实例化 Watcher 时会调用 get 方法(lazy 未显示设置为 true 时),该方法会进行 观察者 watcher 和 依赖 dep 的绑定
Watcher 什么时候进行实例化的?还记得 Vue 实例化一文中,我们提到在调用 $mount
方法时会实例化一个 Watcher
// src\core\instance\lifecycle.js - mountComponent
// vm._render() 创建一个 vnode
// _update 进行 vdom 更新
updateComponent = () => {
vm._update(vm._render(), hydrating)
}
new Watcher(vm, updateComponent, noop, {
before () {
if (vm._isMounted && !vm._isDestroyed) {
callHook(vm, 'beforeUpdate')
}
}
}, true /* isRenderWatcher */)
2
3
4
5
6
7
8
9
10
11
12
13
14
updateComponent
就是 watcher 的 getter 。因此,实例化 Watcher 的时候会调用 updateComponent ,在这个方法中,执行了 vm._render()
,
在内部调用 vm.$options.render 生成了 vnode 。
我们先不管生成 vnode 的细节,大致理解为生成 vnode 时会调用 data 绑定数据的 getter, 此时会生成一个依赖与一开始的 watcher 绑定
举例:
<div id="el">
</div>
<script type="text/x-template" id="demo-template">
<div>{{foo}}</div>
</script>
<script>
new Vue({
el: '#el',
template: '#demo-template',
data: {
foo: "foo",
}
})
</script>
2
3
4
5
6
7
8
9
10
11
12
13
14
15
一开始进行 initData 对 foo 进行属性拦截处理,并在其中实例化了一个 Dep :dep 。然后在 mountComponent 中实例化 Watcher :watcher,实例化过程中会调用 updateComponent ,该方法一开始会执行 vm._render()
,于是调用了 foo 的 getter 。
在 foo 的 getter 里面会执行 dep.depend()
,用于 dep 和 watcher 的绑定,而这是通过 Dep.target
来实现的。
此时 Dep.target
值在调用 updateComponent 前就通过 pushTarget 赋值好了,其值为 watcher 。
于是,watcher 被添加到 foo 依赖 dep 的 subs 列表中;当 foo 改动后,会调用 dep.notify()
通知 watcher 执行 update 方法,最终会调用 run 方法,里面会执行 updateComponent ,然后进行 dom 的更新
整个流程如图所示:
# 说说几个细节
# observe 递归处理子元素
在定义响应式 defineReactive 中,有两个地方进行子元素的处理
一个是一开始的 let childOb = !shallow && observe(val)
, 一个是 set 中的处理 childOb = !shallow && observe(newVal)
let data = {
selected: 0,
options: [
{ id: 1, text: 'Hello' }
]
}
2
3
4
5
6
以此为例,一开始的处理如下:
第一步 observe(data)
, 设置 __ob__
属性, 尝试通过 walk 方法遍历 data 的属性
第二步 defineReactive(data,selected)
,此时处理子元素时 observe(0) 然后直接 return
第三步 defineReactive(data,options)
,此时处理子元素 observe(array), 设置 __ob__
属性,并调用 observeArray 方法处理每一项 observe(items[i])
最后生成的对象为
{
options: [
0: {
id: 1
text: "Hello"
__ob__: Observer {value: {…}, dep: Dep, vmCount: 0}
get id: ƒ reactiveGetter()
set id: ƒ reactiveSetter(newVal)
get text: ƒ reactiveGetter()
set text: ƒ reactiveSetter(newVal)
__proto__: Object
}],
selected: 0
__ob__: Observer {value: {…}, dep: Dep, vmCount: 1}
get options: ƒ reactiveGetter()
set options: ƒ reactiveSetter(newVal)
get selected: ƒ reactiveGetter()
set selected: ƒ reactiveSetter(newVal)
__proto__: Object
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
若 selected 值变成另一个常量,调用 observe(newVal)
后不进行任何处理
若 selected 值变成对象,调用 observe(newVal)
后,执行一开始的流程,处理对象的所有属性
若 options 变为常量,通知观察者更新后, childOb 变为 undefined ,原 childOb 为游离对象,会被垃圾回收
若 options 变为另一个对象,调用 observe(newVal)
后,执行一开始的流程,处理新对象的所有属性,不会复用原 childOb 的处理,原 childOb 会被垃圾回收\
因此我们不要轻易的变更整个对象,避免消耗性能
依赖收集的时候也会遍历子元素. 属性和值都会维护一个 dep ,其中属性对于的 dep 是一个闭包变量
譬如模板上用到了
{{ options[0].text }}
Vue 实例化时创建了一个 watcher.
解析模板时,先触发 options 的 getter ,此时 options 的 dep 会将 watcher 添加进 dep.subs
列表
此时的 childOb 值为
observe([{ id: 1, text: 'Hello' }])
产生的 Observer 对象,childOb.dep.subs = []
接着执行了 childOb.dep.depend()
,于是 options 值对应的 dep 会将 watcher 添加进 dep.subs
列表
由于 childOb 还是数组,因此数组的每一项的值对应的 dep 会将 watcher 添加进
dep.subs
列表
由于数组是直接对每一项进行响应式处理,不响应式处理数组本身属性,于是之后会触发 options[0].text
的 getter
这也就是为什么直接改变数组下标元素不会进行更新的原因
触发 options[0].text
的 getter 后, 此时 options[0].text
的 dep 会将 watcher 添加进 dep.subs
列表,由于没有 childOb ,执行结束
假设模板上只有
{{ options[0].text }}
我们来看下 watcher 一共依赖了多少 dep ?
options 属性的闭包 dep 、 options 值对象的 dep 、 options[0] 值对象的 dep 、 text 属性的闭包 dep
也就是说,以下几种情况,都会通知 watcher 更新:
- 修改整个 options 对象:
options = [{id:2}]
,此时调用的是 options 属性的闭包 dep 的 notify 方法 - 通过数组方法添加元素到 options 中:
options.unshift({id:4})
,此时调用的是 options 值对象的 dep 的 notify 方法protoAugment(value, arrayMethods); 执行某些数组方法后会调用 ob.dep.notify
- 修改 options[0].id 的值:
options[0].id = 3
,此时调用的是 text 属性的闭包 dep 的 notify 方法 - 通过 set 方法设置新属性响应式:
set(options[0],'newId',4)
,此时调用的是 options[0] 值对象的 dep 的 notify 方法执行 notify 后,进行更新时会调用 get 然后进行 newId 闭包变量 dep 与 watcher 的绑定
最后重新编译模板更新 dom
# 防止重复添加依赖
在 Watcher 的 addDep 方法中,做了如下处理
// 给依赖 dep 添加观察者
// 观察者添加依赖 dep
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)
}
}
}
2
3
4
5
6
7
8
9
10
11
12
newDepIds 和 newDeps 的作用是什么?为什么还需要额外的 newDeps 来处理?
我们先找到还有哪里使用了它,可以看到在 cleanupDeps 方法中有用到。
在 cleanupDeps 中,找到不存在于 newDeps 中却存在于 deps 中的依赖,移除观察者 watcher ; 然后将 newDeps 赋值给 deps 并清空 newDeps
cleanupDeps 在 Watcher 的 get 最后执行
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
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
先来解决第一个问题,newDepIds 和 newDeps 的作用是什么?
执行 this.getter.call(vm, vm)
时会可能会调用多次 addDep ,如果处理不当,同一个依赖将多次添加同一个观察者
考虑这个场景
<script type="text/x-template" id="demo-template">
<div v-if="flag">
<div>{{foo}}</div>
<div>{{foo}}</div>
</div>
<div v-else>
<div>{{bar}}</div>
</div>
</script>
<script>
new Vue({
el: '#el',
template: '#demo-template',
data: {
flag: true,
foo: "foo",
bar: "bar",
}
})
</script>
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
初始化 Vue 时会创建一个 Watcher ,那么请问该 watcher 有几个关于 foo 的依赖?答案是 1 个
- 我们一步步分析,一开始
pushTarget(this)
,此时Dep.target = watcher
- 然后调用
this.getter.call(vm, vm)
,开始解析模板 ,由于 flag 为 true - 触发第一个 foo 的 getter ,由于
Dep.target
有值,因此会调用watcher.addDep
,将dep.id
放入 newDepIds 中,dep 添加观察者 watcher - 之后触发第二个 foo 的 getter,由于
Dep.target
有值,因此会调用watcher.addDep
,此时在 newDepIds 中存在dep.id
因此不会进行依赖和观察者的处理 - 最后调用了 popTarget 清空
Dep.target
的值,并将 newDepIds/newDeps 的值赋值给 depIds/deps
所以回答了第一个问题,newDepIds 和 newDeps 用来防止依赖多次添加同一个观察者,当 foo 值变化,只会调用一次 updateComponent 更新 dom
可以提高性能避免不必要的渲染
再问问,当执行 flag = false
后,该 watcher 有几个关于 foo 的依赖?答案是 0 个
可以断点 run 方法,控制台执行
foo = "f"
,发现并没有调用 run ,此时 watcher 的 deps 中没有 foo 的依赖
说明未使用到的依赖会移除观察者,避免无关依赖导致重渲染
- 我们一步步分析,该 watcher 有关于 flag 的依赖,当 flag 变更后,会执行 updateComponent 更新虚拟 dom ,此时解析了 bar 并触发了 getter
- newDepIds/newDeps 的值为空,因此 newDeps 添加了 bar 的依赖,该依赖会添加观察者 watcher
- 之后调用 cleanupDeps ,deps 中有 flag、foo 对应的依赖, newDeps 有 flag、bar 对应的依赖,foo 依赖会移除 watcher 观察者
- 将 newDepIds/newDeps 的值赋值给 depIds/deps
如果没有用另外的 newDeps 保存此次操作添加的依赖,在 cleanupDeps 中不知道应该对哪些依赖进行观察者移除
# 垃圾回收相关
继续说 cleanupDeps 这段代码,其中有个操作是
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
2
3
4
5
6
7
8
9
如果让我写,我应该是
this.depIds = this.newDepIds
this.newDepIds = new Set()
// 注意不能直接 clear ,否则会把 depIds 的也给清空
// this.newDepIds.clear()
this.deps = this.newDeps
this.newDeps = []
// 注意不能直接 length = 0 ,否则 deps 会置空
// this.newDeps.length = 0
2
3
4
5
6
7
8
9
前者的写法可以避免在内存堆不断地申请新空间
两者都会对游离旧数据进行 GC
# 清空观察者
通过 Watcher 的 teardown 方法处理,该方法在 Vue.prototype.$destroy
会被调用,即在 Destroy 生命周期时进行 watcher 的清空
# 观察目标设置
watcher 通过 expOrFn 设置观察目标,当观察目标取值函数被调用时,进行依赖与观察者的绑定
expOrFn 可以是字符串或者方法,当为方法时,即观察目标 getter ;当为字符串时,为数据访问路径,如 data.user.name
,需要对其进行 parsePath 并得到一个方法 getter
export function parsePath (path: string): any {
if (bailRE.test(path)) {
return
}
const segments = path.split('.')
return function (obj) {
for (let i = 0; i < segments.length; i++) {
if (!obj) return
obj = obj[segments[i]]
}
return obj
}
}
2
3
4
5
6
7
8
9
10
11
12
13
# 更新处理
调用观察者的 update 方法后,处理如下:
update () {
/* istanbul ignore else */
if (this.lazy) {
this.dirty = true
} else if (this.sync) {
this.run()
} else {
queueWatcher(this)
}
}
2
3
4
5
6
7
8
9
10
run 即直接执行回调,默认走 queueWatcher ,通过任务队列执行回调
export function queueWatcher (watcher: Watcher) {
const id = watcher.id
// 该任务队列已存在相同观察者,不进行处理
if (has[id] == null) {
has[id] = true
if (!flushing) { // 未刷新队列时,直接入队
queue.push(watcher)
} else {
// 如果处于刷新队列过程中,从后往前找到一个 id 比该 watcher.id 小且还未开始运行的位置插入
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
// ...省略部分代码
// 在下一个微队列中执行 flushSchedulerQueue,
// flushSchedulerQueue :设置 flushing = true,根据 id 重排序 queue 并执行 watcher.run ,
// 回调 Activated Updete 等生命周期方法,最后进行重置
nextTick(flushSchedulerQueue)
}
}
}
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
一开始入队,并在下个微队列中开始 flushing 队列,在 flushing 之前,任务直接入队,在 flushing 时会根据某种规则入队
data 部分的源码就分析到这了,鄙人水平有限,欢迎指正