# Vue响应式原理
# 对象的响应式
初始化data选项,如果data不是对象,则报警告。调用observe(data),对data进行观察。
function initData (vm: Component) {
let data = vm.$options.data
data = vm._data = typeof data === 'function'
? getData(data, vm)
: data || {}
// 如果data不是对象,报警告
if (!isPlainObject(data)) {
data = {}
process.env.NODE_ENV !== 'production' && warn(
'data functions should return an object:\n' +
'https://vuejs.org/v2/guide/components.html#data-Must-Be-a-Function',
vm
)
}
observe(data, true /* asRootData */)
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
执行observe(value)时:
- 如果value不是对象,直接返回;
- 如果对象
__ob__有值,则表示该对象已经被观察过了,直接返回ob(ob是Observer类的实例); - new一个Observer(value)实例,并返回。
function observe (value: any, asRootData: ?boolean): Observer | void {
// 1.如果不是对象直接return
if (!isObject(value) || value instanceof VNode) {
return
}
let ob: Observer | void
// 2.如果已经观测过则直接返回上次观测的实例
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
) {
// 3.如果可以观测(对象、或数组)就进行观测
ob = new Observer(value)
}
// 4.如果是根数据 vmCount标注为1
if (asRootData && ob) {
ob.vmCount++
}
return ob
}
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
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
初始化Observer时,执行其构造函数:
- 给实例添加dep属性:该属性主要用于数组操作,及对象属性的动态添加、删除的监控;
- 如果value是数组;
- 修改数组的原型为arrayMethod,即重写数组的push、pop、shift、unshift、splice、sort、reverse这7个方法,这些方法会修改数组本身。
- 执行
observeArray(value),对数组的每一项进行观察。
- 如果value是一个普通对象
- 执行
walk(value),循环value的每一个key,通过defineReactive(obj, keys[i]),重写getter、setter。
- 执行
class Observer {
value: any;
dep: Dep;
vmCount: number; // number of vms that have this object as root $data
constructor (value: any) {
this.value = value
// 每个对象都一个自己的dep(主要用于数组的依赖收集)
this.dep = new Dep()
this.vmCount = 0
// value.__ob__赋值为Observer的实例,即每个value都有个一个__ob__属性,指向value对应的Observer实例。
def(value, '__ob__', this)
// 1、如果是数组,重写数组方法(push,pop,shift,unshift,splice,sort,reverse)
if (Array.isArray(value)) {
if (hasProto) { // (__proto__ in {})
// 1)将value的原型__proto__,指向arrayMethods,用于拦击数组方法(这7个方法会改变数组本身,所以需要拦截)
protoAugment(value, arrayMethods)
} else {
// 2)或者定义value.method为arrayMethods[method]方法
copyAugment(value, arrayMethods, arrayKeys)
}
// 对数组进行观察
this.observeArray(value)
} else {
// 2、如果是对象,最终调用defineReactive将其变为响应式的
this.walk(value)
}
}
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])
}
}
}
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
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
# 数组原型的重写
在上面我们说过,当value是一个数组的时候,我们重写了push,pop,shift,unshift,splice,sort,reverse这7个方法。当我们通过以上方法对数组操作时,就可以监控到用户的操作。
- 通过
arrayProto = Object.create(arrayProto)方法,创建一个指向数组原型的对象副本 - 给arrayMethods对象添加这7个方法;
- 其中
push、unshift、slice可能会给数组添加新的数据。我们将新添加的数据存放到inserted数组中,此时对新添加的数据,通过observeArray(inserted),依次对数组内的每个对象进行监控。 - 注意,
const ob = this.__ob__这段代码。this为当前数组对象value。在new Observer(value)阶段,给value添加了一个__ob__属性,指向value对应的Observer实例。因此此时this.__ob__代表的是value的Observer实例ob,它有自己的dep实例。
- 其中
- 调用
ob.dep.notify(),通知页面更新。
// array.js
import { def } from '../util/index'
const arrayProto = Array.prototype
// arrayMethod的隐式原型:__proto__, 指向Array.prototype
export const arrayMethods = Object.create(arrayProto)
const methodsToPatch = [ 'push', 'pop', 'shift', 'unshift', 'splice', 'sort', 'reverse' ]
/**
* Intercept mutating methods and emit events
*/
methodsToPatch.forEach(function (method) {
// cache original method
const original = arrayProto[method]
// 重写数组的"push,pop,shift,unshift,splice,sort,reverse"这7个方法
def(arrayMethods, method, function mutator (...args) {
// apply,调用数组原来的方法
const result = original.apply(this, args)
// this就是我们的数组对象target,this.__ob__就是其Observer实例对象。
const ob = this.__ob__
let inserted
switch (method) {
case 'push':
// push、unshift一样,关于switch语句的用法,可以参考https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Statements/switch
case 'unshift':
// unshift,添加到数组头部
inserted = args
break
case 'splice':
// splice(start,deletCount, item1,item2),从第二个开始,是要添加的数据
inserted = args.slice(2)
break
}
// 如果inserted有数据,则对新添加的数组进行观察
if (inserted) ob.observeArray(inserted)
// notify change,重新渲染
ob.dep.notify();
// result:原生数组执行后的返回值
return result
})
})
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
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
# defineReactive响应式
通过Object.defineProperty,重写对象key的getter、setter。
- 如果value本身也是对象,则调用
observe(value)方法,对value进行递归观察。 - 对key取值时,触发get方法,对key依赖的watcher进行收集
- 执行
dep.depend(),对当前key的dep进行依赖收集。如果value是对象时,则此时childOb(子对象Observer实例)不为空,调用childOb.dep.depend()对子dep进行依赖收集。 - 如果value是数组,调用
dependArray,循环数组的每一项,执行depend方法,递归收集数组的依赖。
- 执行
function defineReactive (
obj: Object,
key: string,
val: any,
customSetter?: ?Function,
shallow?: boolean
) {
const dep = new Dep()
// val是对象(数组),则递归observe(val),返回子对象的Observer实例:childOb
let childOb = observe(val)
// 3、重新定义getter、setter
Object.defineProperty(obj, key, {
enumerable: true,
configurable: true,
// 依赖收集,每一个dep收集对应的watcher
get: function reactiveGetter () {
const value = getter ? getter.call(obj) : val
if (Dep.target) {
dep.depend();
if (childOb) { // 新设置的val是一个对象(数组),此时childOb存在
childOb.dep.depend(); // childOb这个observer实例,去收集当前的watcher
// 如果getter的执行结果value是一个数组,则对数组中的内容继续进行依赖收集
if (Array.isArray(value)) { // 递归收集数组中的依赖
// 如果数组中还有数组,例如"[[20,21],1,2]", 当我们通过vm.arr[0].push(100)时,也能触发更新
dependArray(value)
}
}
}
return value
},
// 派发更新,
// 循环dep中的watchers,执行watcher.update -> queneWatcher -> nextTick(flushSchedulerQueue -> 依次执行watcher.run方法)
set: function reactiveSetter (newVal) {
const value = getter ? getter.call(obj) : val;
if (newVal === value || (newVal !== newVal && value !== value)) {
return
}
childOb = observe(newVal)
// 每个key都有自己的dep
dep.notify()
}
})
}
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
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
function dependArray (value: Array<any>) {
for (let e, i = 0, l = value.length; i < l; i++) {
e = value[i]
e && e.__ob__ && e.__ob__.dep.depend()
if (Array.isArray(e)) {
dependArray(e)
}
}
}
1
2
3
4
5
6
7
8
9
2
3
4
5
6
7
8
9
# 响应式流程分析
下图就是Vue中,实现对象响应式的整个过程。
总结起来如下:
# 1、观察阶段
- 只有对象才可以被observe;
- observe(value)时,初始化一个Observer实例:
- value是数组:
- 则重写数组方法。当我们调用
push,pop,shift,unshift,splice,sort,reverse这七个方法时,调用this.__ob__.dep.notify()通知页面更新。 - 调用observeArray(value),对数组内的每一项进行观察。因为数组内的项可能有对象
- 则重写数组方法。当我们调用
- 如果value是对象:
- value是数组:
# 2、依赖收集阶段
当我们对一个对象的key取值时(假设值为value),触发其getter,如果Dep.target不为空,则调用dep.depend()收集当前key依赖的watcher。因为childOb可能不为空(对象key对应的值可能也是对象),因此需要对该对象进行依赖收集,即childOb.dep.depend()。
- 如果这个对象value是数组,则调用
dependArray,循环数组的每一项进行依赖收集。因为这个数组内部可能还有嵌套的数组。
# 3、更新阶段
当我们对一个对象的key设置新的值value
- 新值value可能也是对象,调用observe(value)对新设置的值进行观察;
- 调用dep.notify,通知页面更新。
至此,对象的响应式处理完成,包括设置响应式、收集依赖、更新这3个阶段。