banner
Hi my new friend!

深入响应式原理

Scroll down

变化侦测

什么是变化侦测

变化侦测就是追踪状态,或者说是追踪数据的变化,一旦数据发生了变化,就要去更新视图。

变化侦测可不是个新名词,它在目前的前端三大框架中均有涉及。在Angular中是通过脏值检查流程来实现变化侦测;在React是通过对比虚拟DOM来实现变化侦测,而在Vue中也有自己的一套变化侦测实现机制。

Vue是怎么知道state(状态)变化了呢?换句话说,数据变化了是怎么通知给Vue呢?

如何追踪变化

JS中,如何侦测一个对象的变化? (对象的属性值改变)

ES5 => Object.defineProperty

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
function defineReactive(obj, key, val){
Object.defineProperty(obj, key, {
configurable:true,
enumerable:true,
get: function reactiveGetter(){
// 读obj.key触发getter
console.log('getter触发')
return val
},
set: function reactiveSetter(newVal){
// 写obj.key触发setter
console.log('setter触发')
if (val === newVal) return
val = newVal
}
})
}
// 封装了这个代码之后,对一个对象的属性变化,我们就可以做一些监听了
const obj = {
age:18
}
defineReactive(obj,'age', 18)
console.log(obj)

封装好defineReactive之后,

每当从data的key中读取数据时,get函数被触发;

每当往data的key中 设置数据时,set函数被触发

ES6 => Proxy

Object.defineProperty

Vue.js 实现响应式的核心是利用了 ES5 的 Object.defineProperty - link-MDN

作用:在一个对象上定义一个新属性,或者修改一个对象的现有属性, 并返回这个对象

1
2
3
4
5
6
7
8
/*
obj: 目标对象
prop: 需要操作的目标对象的属性名
descriptor: 描述符

return value 传入的对象
*/
Object.defineProperty(obj, prop/key, descriptor)

obj 是要在其上定义属性的对象;prop 是要定义或修改的属性的名称;descriptor 是将被定义或修改的属性描述符。

descriptor的一些属性,简单介绍几个属性,具体可以参考 MDN 文档

  • enumerable,控制属性是否可枚举,默认 false。
  • configurable,属性描述符是否可以修改,属性是否可以删除,默认 false。
  • get,获取属性的方法。
  • set,设置属性的方法。

这里我们最关心的是 getset

  • get 是一个给属性提供的 getter 方法,当我们访问了该属性的时候会触发 getter 方法;

  • set 是一个给属性提供的 setter 方法,当我们对该属性做修改的时候会触发 setter 方法。

一旦对象拥有了 getter 和 setter,我们可以简单地把这个对象称为响应式对象

defineReactive

src\core\observer\index.ts

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
export function defineReactive(
obj: object,
key: string,
val?: any,
customSetter?: Function | null,
shallow?: boolean,
mock?: boolean
) {
/*!! 在定义一个dep对象,getter中进行依赖收集,闭包*/
const dep = new Dep()

// 取出某个属性的descriptor描述对象
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) &&
(val === NO_INITIAL_VALUE || arguments.length === 2)
) {
val = obj[key]
}
//!! /*对象的子对象递归进行observe并返回子节点的Observer对象*/
let childOb = !shallow && observe(val, false, mock)

// Object.defineProperty ==> 核心的API 这个!!! initData ==>
Object.defineProperty(obj, key, {
enumerable: true,
configurable: true,
get: function reactiveGetter() {
// 如果原本对象这个属性拥有getter方法则执行
const value = getter ? getter.call(obj) : val

// 依赖收集 ---start
// Dep.target当前渲染watcher
if (Dep.target) {
if (__DEV__) {
dep.depend({
target: obj,
type: TrackOpTypes.GET,
key
})
} else {
/*进行依赖收集*/
/*实际上调用了 Dep.target.addDep(this)这个方法*/
// addDep实际上是watcher定义的
dep.depend()
}
if (childOb) {
/*子对象进行依赖收集,其实就是将同一个watcher观察者实例放进了两个depend中,一个是正在本身闭包中的depend,另一个是子元素的depend*/
childOb.dep.depend()
if (isArray(value)) {
/*是数组则需要对每一个成员都进行依赖收集,如果数组的成员还是数组,则递归。*/
dependArray(value)
}
}
}
// 依赖收集 --- end
// return value
return isRef(value) && !shallow ? value.value : value
},
set: function reactiveSetter(newVal) {
/*通过getter方法获取当前值,与新值进行比较,一致则不需要执行下面的操作*/
const value = getter ? getter.call(obj) : val
if (!hasChanged(value, newVal)) {
return
}
if (__DEV__ && customSetter) {
customSetter()
}
if (setter) {
/*如果原本对象的属性拥有setter方法则执行setter*/
setter.call(obj, newVal)
} else if (getter) {
// #7981: for accessor properties without setter
return
} else if (!shallow && isRef(value) && !isRef(newVal)) {
value.value = newVal
return
} else {
val = newVal
}
/*新的值需要重新进行observe,保证数据响应式*/
// childOb = observe(newVal)
childOb = !shallow && observe(newVal, false, mock)
if (__DEV__) {
dep.notify({
type: TriggerOpTypes.SET,
target: obj,
key,
newValue: newVal,
oldValue: value
})
} else {
// 通知依赖更新,更新视图
// 通知所有用到data中变化的那个属性的,所有组件 都更新视图
// 依赖收集:收集用到了data某个属性的所有组件
/*dep对象通知所有的观察者*/
dep.notify()
}
}
})

return dep
}
  1. 响应式对象的核心是利用Object.defineProperty给对象的属性添加 getter 和 setter,

  2. Vue会把props,data变成响应式对象,在定义响应式对象的过程中,如果data对象子属性也为对象,则递归的把该对象的子对象也变为响应式的。

依赖收集

Qestion:

  1. 什么是依赖收集?
  2. 在哪里做的依赖收集?
  3. 收集的依赖放到存到了哪里?

什么是依赖收集

data中的数据变为的可观测以后,我们就能知道数据什么时候发生了变化,那么当数据发生变化时,我们去通知视图更新就好了。

那么问题又来了,视图那么大,组件那么多,我们到底该通知谁去变化(哪个组件更新视图)?总不能一个数据变化了,把整个视图全部更新一遍吧,这样显然是不合理的。大家肯定会想到,视图里谁用到了这个数据就更新谁呗。是滴,尤大大也是这么想的~~

视图里谁用到了这个数据就更新谁,我们换个优雅说法:我们把”谁用到了这个数据”称为”谁依赖了这个数据”, => 哪些地方依赖了data中的数据?

我们把用到data中数据的地方,都叫做依赖,比如一个组件template中p标签里渲染了, 那么这个组件的这个dom元素就依赖了msg这个数据,但是在Vue2中,我们把用到数据的这个组件叫做 msg数据 的 依赖。

  • vue1.x,细粒度依赖,用到数据的 DOM 都是依赖;
  • Vue2.x,中等粒度依赖,用到数据的 组件 是依赖;

我们给每个数据都建一个依赖数组(因为一个数据可能被多处使用),谁依赖了这个数据(即谁用到了这个数据)我们就把谁放入这个依赖数组中,那么当这个数据发生变化的时候,我们就去它对应的依赖数组中,把每个依赖都通知一遍,告诉他们:”你们依赖的数据变啦,你们该更新啦!”。这个过程就是依赖收集。

如何收集依赖

Dep类

src\core\observer\dep.ts

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
import config from '../config'
import { DebuggerOptions, DebuggerEventExtraInfo } from 'v3'

let uid = 0

const pendingCleanupDeps: Dep[] = []

export const cleanupDeps = () => {
for (let i = 0; i < pendingCleanupDeps.length; i++) {
const dep = pendingCleanupDeps[i]
dep.subs = dep.subs.filter(s => s)
dep._pending = false
}
pendingCleanupDeps.length = 0
}

/**
* @internal
*/
export interface DepTarget extends DebuggerOptions {
id: number
addDep(dep: Dep): void
update(): void
}

/**
* A dep is an observable that can have multiple
* directives subscribing to it.
* @internal
*/

// 建立数据和watcher之前的桥梁
export default class Dep {
static target?: DepTarget | null
id: number
subs: Array<DepTarget | null>
// pending subs cleanup
_pending = false

constructor() {
this.id = uid++
/* 订阅数据变化的watcher会保存到subs数组中 */
this.subs = []
}

addSub(sub: DepTarget) {
this.subs.push(sub)
}

removeSub(sub: DepTarget) {
// #12696 deps with massive amount of subscribers are extremely slow to
// clean up in Chromium
// to workaround this, we unset the sub for now, and clear them on
// next scheduler flush.
this.subs[this.subs.indexOf(sub)] = null
if (!this._pending) {
this._pending = true
pendingCleanupDeps.push(this)
}
}

depend(info?: DebuggerEventExtraInfo) {
if (Dep.target) {
// 调用watcher的addDep方法
Dep.target.addDep(this)
if (__DEV__ && info && Dep.target.onTrack) {
Dep.target.onTrack({
effect: Dep.target,
...info
})
}
}
}

notify(info?: DebuggerEventExtraInfo) {
// stabilize the subscriber list first
const subs = this.subs.filter(s => s) as DepTarget[]
if (__DEV__ && !config.async) {
// subs aren't sorted in scheduler if not running async
// we need to sort them now to make sure they fire in correct
// order
subs.sort((a, b) => a.id - b.id)
}
for (let i = 0, l = subs.length; i < l; i++) {
const sub = subs[i]
if (__DEV__ && info) {
sub.onTrigger &&
sub.onTrigger({
effect: subs[i],
...info
})
}
sub.update()
}
}
}

// The current target watcher being evaluated.
// This is globally unique because only one watcher
// can be evaluated at a time.
Dep.target = null
const targetStack: Array<DepTarget | null | undefined> = []

export function pushTarget(target?: DepTarget | null) {
// targetStack 嵌套组件
targetStack.push(target)
Dep.target = target
}

export function popTarget() {
targetStack.pop()
Dep.target = targetStack[targetStack.length - 1]
}

Watcher类

src\core\observer\watcher.ts

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
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
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
export default class Watcher implements DepTarget {
vm?: Component | null
expression: string
cb: Function
id: number
deep: boolean
user: boolean
lazy: boolean
sync: boolean
dirty: boolean
active: boolean
deps: Array<Dep>
newDeps: Array<Dep>
depIds: SimpleSet
newDepIds: SimpleSet
before?: Function
onStop?: Function
noRecurse?: boolean
getter: Function
value: any
post: boolean

// dev only
onTrack?: ((event: DebuggerEvent) => void) | undefined
onTrigger?: ((event: DebuggerEvent) => void) | undefined

constructor(
vm: Component | null,
expOrFn: string | (() => any),
cb: Function,
options?: WatcherOptions | null,
isRenderWatcher?: boolean
) {
recordEffectScope(
this,
// if the active effect scope is manually created (not a component scope),
// prioritize it
activeEffectScope && !activeEffectScope._vm
? activeEffectScope
: vm
? vm._scope
: undefined
)
if ((this.vm = vm) && isRenderWatcher) {
vm._watcher = this
}
// options
if (options) {
this.deep = !!options.deep
this.user = !!options.user
this.lazy = !!options.lazy
this.sync = !!options.sync
this.before = options.before
if (__DEV__) {
this.onTrack = options.onTrack
this.onTrigger = options.onTrigger
}
} else {
this.deep = this.user = this.lazy = this.sync = false
}
this.cb = cb
this.id = ++uid // uid for batching
this.active = true
this.post = false
this.dirty = this.lazy // for lazy watchers ==> computed
// 注意这里
this.deps = []
this.newDeps = []
this.depIds = new Set()
this.newDepIds = new Set()
// end
this.expression = __DEV__ ? expOrFn.toString() : ''
// parse expression for getter
// 如果传入的第二个参数是一个函数,updateComponent哪里
if (isFunction(expOrFn)) {
// 如果是一个函数,把这个函数放到watcher实例对象的getter属性上
/*把表达式expOrFn解析成getter*/
this.getter = expOrFn
} else {
this.getter = parsePath(expOrFn)
this.value = this.lazy ? undefined : this.get()
}

/**
* Evaluate the getter, and re-collect dependencies.
*/
get() {
// Dep类中定义
pushTarget(this)
let value
const vm = this.vm
try {
// try-catch-finally => 重要的是try
// 这里的调用getter,实际上是调用updateComponent方法
value = this.getter.call(vm, vm)
} catch (e: any) {
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
// 判断newDepIds中没有没
if (!this.newDepIds.has(id)) {
// 没有就添加id,并push dep到DepDes中
this.newDepIds.add(id)
this.newDeps.push(dep)
// 如果depIds中也没有
if (!this.depIds.has(id)) {
// Dep类中的addSub方法,把这个watcher实例添加到Dep类的subs中
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: any = 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) {
const info = `callback for watcher "${this.expression}"`
invokeWithErrorHandling(
this.cb,
this.vm,
[value, oldValue],
this.vm,
info
)
} 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.vm && !this.vm._isBeingDestroyed) {
remove(this.vm._scope.effects, this)
}
if (this.active) {
let i = this.deps.length
while (i--) {
this.deps[i].removeSub(this)
}
this.active = false
if (this.onStop) {
this.onStop()
}
}
}
}

组件的watcher跟计算属性以及watch是不一样的一个watcher , 是不同的watcher实例

Observer类

src\core\observer\index.ts

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
export class Observer {
dep: Dep
vmCount: number // number of vms that have this object as root $data

constructor(public value: any, public shallow = false, public mock = false) {
// this.value = value
this.dep = mock ? mockDep : new Dep()
this.vmCount = 0
// 将Observer实例挂载到data的__ob__属性上,
// 相当于是给对象是否做了响应式打了一个标记,在调用observe的时候,会判断是否有这个标记
// 如果对一个对象做过了响应式处理了,就不需要再做一遍
def(value, '__ob__', this)
if (isArray(value)) {
if (!mock) {
if (hasProto) {
/* eslint-disable no-proto */
;(value as any).__proto__ = arrayMethods
/* eslint-enable no-proto */
} else {
for (let i = 0, l = arrayKeys.length; i < l; i++) {
const key = arrayKeys[i]
def(value, key, arrayMethods[key])
}
}
}
if (!shallow) {
this.observeArray(value)
}
} else {

/**
* 遍历对象上的每个key(属性),将它们转为带有getter和setter的属性,
* 这个方法只有当value是对象的时候才会执行,(原来2.6的版本是写的walk方法)
*/
/**
* Walk through all properties and convert them into
* getter/setters. This method should only be called when
* value type is Object.
*/
const keys = Object.keys(value)
// data里面 可能有很多的属性 ,for循环,对每一个key都做响应式处理
for (let i = 0; i < keys.length; i++) {
const key = keys[i]
// 做响应式处理~~~ defineReactive
defineReactive(value, key, NO_INITIAL_VALUE, undefined, shallow, mock)
}
}
}

/**
* Observe a list of Array items.
*/
observeArray(value: any[]) {
for (let i = 0, l = value.length; i < l; i++) {
observe(value[i], false, this.mock)
}
}
}

arrayMethods

src\core\observer\array.ts

1
2
3
4
5
6
7
8
9
10
11
12
const arrayProto = Array.prototype
export const arrayMethods = Object.create(arrayProto)

const methodsToPatch = [
'push',
'pop',
'shift',
'unshift',
'splice',
'sort',
'reverse'
]

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
/**
* 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.
*/
export function observe(
value: any,
shallow?: boolean,
ssrMockReactivity?: boolean
): Observer | void {
if (value && hasOwn(value, '__ob__') && value.__ob__ instanceof Observer) {
return value.__ob__
}
if (
shouldObserve &&
(ssrMockReactivity || !isServerRendering()) &&
(isArray(value) || isPlainObject(value)) &&
Object.isExtensible(value) &&
!value.__v_skip /* ReactiveFlags.SKIP */ &&
!isRef(value) &&
!(value instanceof VNode)
) {
// 执行 Observer构造函数 ,得到了一个实例对象
return new Observer(value, shallow, ssrMockReactivity)
}
}

mountComponent

src\core\instance\lifecycle.ts

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
export function mountComponent(
vm: Component,
el: Element | null | undefined,
hydrating?: boolean
): Component {
vm.$el = el
if (!vm.$options.render) {
vm.$options.render = createEmptyVNode
}
callHook(vm, 'beforeMount')

let updateComponent
if (__DEV__ && config.performance && mark){
// ....
} else {
// 重要!这里是声明~~
// 当执行这个updateComponent的过程中,实际上又会执行vm._render()
updateComponent = () => {
vm._update(vm._render(), hydrating)
}
}

const watcherOptions: WatcherOptions = {
before() {
if (vm._isMounted && !vm._isDestroyed) {
callHook(vm, 'beforeUpdate')
}
}
}

if (__DEV__) {
watcherOptions.onTrack = e => callHook(vm, 'renderTracked', [e])
watcherOptions.onTrigger = e => callHook(vm, 'renderTriggered', [e])
}

// we set this to vm._watcher inside the watcher's constructor
// since the watcher's initial patch may call $forceUpdate (e.g. inside child
// component's mounted hook), which relies on vm._watcher being already defined
new Watcher(
vm,
updateComponent,
noop,
watcherOptions,
true /* isRenderWatcher */
)
hydrating = false

// flush buffer for flush: "pre" watchers queued in setup()
const preWatchers = vm._preWatchers
if (preWatchers) {
for (let i = 0; i < preWatchers.length; i++) {
preWatchers[i].run()
}
}

// manually mounted instance, call mounted on self
// mounted is called for render-created child components in its inserted hook
if (vm.$vnode == null) {
vm._isMounted = true
callHook(vm, 'mounted')
}
return vm
}

_render

src\core\instance\render.ts

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
// 1.
updateComponent = () => {
// 调用updateComponent时,会执行render函数
vm._update(vm._render(), hydrating)
}
// 2.
Vue.prototype._render = (){
// 执行render函数的时候,实际上会读取模板中定义的data数据,也就会触发defineReactive中的getter函数,收集依赖
vnode = render.call(vm._renderProxy, vm.$createElement)
}
// 3. ==> 触发defineReactive中的getter函数
dep.depend()
// 4. ==> 实际上调用watcher的addDep方法
Dep.target.addDep(this) => (){ dep.addSub}
// 5. 在watcher的addDep方法中,又会调用Dep类中的addSub方法
// 把这个watcher实例添加到Dep类的subs中
dep.addSub(this)
// 6. 最终执行dep.ts 中 this.subs.push(sub) => 把watcher放到subs中
this.subs.push(sub)

// 收集的时Dep.target => 当前正在计算的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
Vue.prototype._render = function (): VNode {
const vm: Component = this
const { render, _parentVnode } = vm.$options

if (_parentVnode && vm._isMounted) {
vm.$scopedSlots = normalizeScopedSlots(
vm.$parent!,
_parentVnode.data!.scopedSlots,
vm.$slots,
vm.$scopedSlots
)
if (vm._slotsProxy) {
syncSetupSlots(vm._slotsProxy, vm.$scopedSlots)
}
}

// set parent vnode. this allows render functions to have access
// to the data on the placeholder node.
vm.$vnode = _parentVnode!
// render self
let vnode
try {
// There's no need to maintain a stack because all render fns are called
// separately from one another. Nested component's render fns are called
// when parent component is patched.
setCurrentInstance(vm)
currentRenderingInstance = vm
// 执行render函数
vnode = render.call(vm._renderProxy, vm.$createElement)
} catch (e: any) {
handleError(e, vm, `render`)
// return error render result,
// or previous vnode to prevent render error causing blank component
/* istanbul ignore else */
if (__DEV__ && vm.$options.renderError) {
try {
vnode = vm.$options.renderError.call(
vm._renderProxy,
vm.$createElement,
e
)
} catch (e: any) {
handleError(e, vm, `renderError`)
vnode = vm._vnode
}
} else {
vnode = vm._vnode
}
} finally {
currentRenderingInstance = null
setCurrentInstance()
}
// if the returned array contains only a single node, allow it
if (isArray(vnode) && vnode.length === 1) {
vnode = vnode[0]
}
// return empty vnode in case the render function errored out
if (!(vnode instanceof VNode)) {
if (__DEV__ && isArray(vnode)) {
warn(
'Multiple root nodes returned from render function. Render function ' +
'should return a single root node.',
vm
)
}
vnode = createEmptyVNode()
}
// set parent
vnode.parent = _parentVnode
return vnode
}

小结

  1. 依赖收集 就是 订阅数据变化的 watcher 的收集
  2. 依赖收集的目的就是当这些响应式数据发生变化,触发了它们的setter的时候,能知道通知哪些订阅者去做相应的逻辑处理(视图更新)

参考

  1. Vue2-源码解析 https://ustbhuangyi.github.io/vue-analysis/
  2. Vue2-源码 https://vue-js.com/learn-vue/
  3. React-源码解析https://react.iamkasong.com/
  4. https://www.cnblogs.com/buluzombie/p/15204328.html - 依赖收集
  5. https://www.bilibili.com/video/BV1Ti4y1w7TL?p=5&vd_source=df92e41736899cbcfe83bba6d67cad3c
  6. https://blog.csdn.net/hd101367816/article/details/114041232
  7. https://blog.csdn.net/hd101367816/article/details/114042951
  8. 纯干货!图解Vue响应式原理
  9. Vue 源码解读(3)—— 响应式原理
  10. 当面试官问你Vue响应式原理,你可以这么回答他
  11. 【大型干货】手拉手带你过一遍vue部分源码
  12. 图解 Vue 响应式原理
  13. 图解
  14. Vue源码阅读-https://github.com/liyongning/blog
  15. Vue响应式原理-理解Observer、Dep、Watcher
  16. 你可以手写Vue2的响应式原理吗?
  17. 为什么说 Vue 的响应式更新精确到组件级别?(原理深度解析)
  18. 两年阿里
  19. 尤雨溪国外教程:亲手带你写个简易版的Vue!

PS.扩展知识

Object.getOwnPropertyDescriptor

返回指定对象上一个自有属性对应的属性描述符(自有属性指的是直接赋予该对象的属性,不需要从原型链上进行查找的属性)

1
2
3
4
5
6
7
8
9
10
11
12
const obj = {
name:'hello'
}
// const res = Object.getOwnPropertyDescriptor(obj,key)
const res = Object.getOwnPropertyDescriptor(obj, 'name')

res => {
configurable: true
enumerable: true
value: "hello"
writable: true
}

typora快捷键

常用快捷键

功能 快捷键
代码 Ctrl+Shift+`
标题1~6 Ctrl + 1/2/3/4/5/6
段落 Ctrl+0
提高/降低标题 Ctrl + 提高 Ctrl - 降低
表格 Ctrl + T
引用 Ctrl + Shift + Q 或 > + 空格
加粗 Ctrl + B
倾斜 Ctrl + I
下划线 Ctrl + U
选中一整行 Ctrl + L
选中单词 Ctrl + D
分割线 +++ 回车 / — 回车
表格新增一行 Ctrl + Enter
超链接 Ctrl + K
打开大纲视图 Ctrl + Shift + 1
打开文档列表视图 Ctrl + Shift + 2
打开文件树视图 Ctrl + Shift + 3
显示隐藏侧边栏 Ctrl + Shift + L
放大 Ctrl + Shift + +
缩小 Ctrl + Shift + -
100%大小 Ctrl + Shift + 0
快速打开 Ctrl + P
打开 Ctrl + O
新建 Ctrl + N
新建窗口 Ctrl + Shift + N
其他文章