变化侦测

Object 侦测

vue object 侦测

递归侦测所有key

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
/**
* Observer类会附加到每一个被侦测到的object上
* 一旦被附加上,Observer会将object的所有属性转换为getter/setter的形式
* 来手机属性的依赖,并且当属性发生变化时会通知这些依赖
*/
export class Observer {
constructor (value) {
this.value = value
if(!Array.isArray(value)) { //只有Object类型的数据才会调用walk
this.walk(value)
}
}
/**
* walk会将每一个属性都转换成getter/setter的形式来侦测变化
*/
walk(obj) {
const keys = Object.keys(obj)
for (let i = 0; i < key.length; i++) {
defineReactive(obj, keys[i], obj[keys[i]])
}
}
}

// 在这个函数中进行变化追踪
function defineReactive(data, key, val) {
//新增, 递归子属性
if (typeof val === 'object') {
new Observer(val)
}
let dep = new Dep()
Object.defineProperty(data, key, {
enumerable: true,
configurable: true,
get: function () {
dep.depend() // 修改
return val
},
set: function (newVal) {
if(val === newVal) {
return
}

val = newVal
dep.notify() // 新增
}
})
}

// 管理依赖
export default class Dep {
constructor () {
this.subs = []
}
addSub (sub) {
this.subs.push(sub)
}
removeSub (sub) {
remove(this.subs, sub)
}
depend () {
if (window.target) {
this.addSub(window.target)//假设依赖是一个函数,保存在window.target上
}
}
notify () {
const subs = this.subs.slice()
for (let i = 0, l = subs.length; i < l; i++) {
subs[i].update()
}
}
}
function remove (arr, item) {
if (arr.length) {
const index = arr.indexOf(item)
if(index > -1) {
return arr.splice(index, 1)
}
}
}

// Watcher是一个中介角色,数据发生变化时通知它,然后它再通知其他地方.
export default class Watcher {
constructor (vm, expOrFn, cb) {
this.vm = vm
this.getter = parsePath(expOrFn)//执行this.getter(),就可以读取data.a.b.c的内容
this.cb = cb
this.value = this.get()
}
get() {
window.target = this
let value = this.getter.call(this.vm, this.vm)
window.target = undefined
return value
}
update() {
const oldValue = this.value
this.value = this.get()
this.cb.call(this.vm, this.value, oldValue)
}
}

Array 侦测

Array追踪变化的方式和Object不一样.因为它是通过方法改变内容的,所以我们通过创建拦截器去覆盖数组原型的方式来追踪变化.

为了不污染全局Array.prototype,我们在Observer中只针对那些需要侦测变化的数组使用_proto_来覆盖原型方法,但ES6之前并不是标准属性,针对不支持的游览器,直接循环拦截器,把拦截器中的方法直接设置到数组身上拦截Array.prototype上的原生方法.

Array收集依赖的方式和Object一样,都是在getter中收集.但是由于使用依赖的位置不同,数组要在拦截器中向依赖发送消息,所以依赖不能像Object那样保存在defineReactive中,而是把依赖保存在了Observer实例上.

在Observer中,对每个侦测了变化的数据都标上印记_ob_,并把this(Observer实力)保存在_ob_上.主要有两个作用,一方面是为了标记数据是否被侦测到,另一方面可以很方便的通过数据取到_ob_,从而拿到Observer实例上保存的依赖.当拦截到数组发生到变化时,向依赖发送通知.

除侦测数组的自身变化外,数组中元素发生的变化也要侦测.我们在Observer中判断如果当前被侦测的数据是数组,则调用observeArray方法将数组中的每一个元素都转换成响应式并侦测变化.

当用户使用push等方法像数组中新增数据时,新增的数据也要进行变化侦测.如果是push,unshift,splice方法,从参数中将新增数据提取出来,然后使用observeArray对新增数据进行变化侦测.
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
// 拦截器
const arrayProto = Array.prototype
export const arrayMethods = Object.create(arrayProto)

// ;[
// 'push',
// 'pop',
// 'shift',
// 'unshift',
// 'splice',
// 'sort',
// 'reverse'
// ].forEach(function (method) {
// const original = arrayProto[method] //缓存原始方法
// Object.defineProperty(arrayMethods, method, {
// value: function mutator (...args) {
// const ob = this.__ob__ // 获取Observer实例
// return original.apply(this, args)
// },
// enumerable: false,
// writable: true,
// configurable: true
// })
// })

;[
'push',
'pop',
'shift',
'unshift',
'splice',
'sort',
'reverse'
].forEach(function (method) {
const original = arrayProto[method] //缓存原始方法
def(arrayMethods, method, function mutator (...args) {
const result = original.apply(this, args)
const ob = this.__ob__ // 获取Observer实例
let inserted
switch (method) {
case 'push':
case 'unshift':
inserted = args
break
case 'splice':
inserted = args.slice(2)
break

}
if (inserted) ob.observeArray(inserted) // push,unshift,splice新增元素的变化侦测
ob.dep.notify() // 向依赖发送消息
return result
})
})

// 使用拦截器覆盖Array原型
import { arrayMethods } from './array'
const hasProto = '__proto__' in {} // __proto__是否可用
const arrayKeys = Object.getOwnPropertyNames(arrayMethods)

function def (obj, key, enumerable) {
Object.defineProperty(obj, key, {
value: val,
enumerable: !!enumerable,
writable: true,
configurable: true
})
}

export class Observer {
constructor (value) {
this.value = value
this.dep = new Dep() // 新增Dep
def(value, '__ob__', this) // 在value上新增一个属性,这个属性的值就是Observer实例

if(Array.isArray(value)) {
// const augment = hasProto ? protoAugment : copyAugment
// augment(value, arrayMethods, arrayKeys)
this.observeArray(value) // 把这个数据的所有子数据转换成响应式的
//value.__proto__ = arrayMethods //修改前
} else {
this.walk(value)
}
}
/**
* 侦测Array中的每一项
*/
observeArray (items) {
for (let i =0, l = items.length; i < l; i++) {
const augment = hasProto ? protoAugment : copyAugment
augment(value, arrayMethods, arrayKeys)
}
}
}



function protoAugment (target, src, keys) {
target.__proto__ = src
}
function copyAugment (target, src, keys) {
for (let i = 0, l=keys.length; i < l; i++) {
const key = keys[i]
def(target, key, src[key])
}
}

// 收集依赖
function defineReactive (data, key, val) {
let childOb = observe(val)
let dep = new Dep()
Object.defineProperty(data, key, {
enumerable: true,
configurable: true,
get: function () {
dep.depend()

if (childOb) {
childOb.dep.depend() // 收集Array的依赖
}
return val
},
set: function (newVal) {
if(val === newVal) {
return
}
dep.notify()
val = newVal
}
})
}

/**
* 尝试为value创建一个Observer实力
* 如果创建成功, 直接返回新创建的Observer实例
* 如果value已经存在一个Observer实例,则直接返回它
*/
export function observe (value, asRootData) {
if (!isObject(value)) {
return
}
let ob
if (hasOwn(value, '__ob__') && value.__ob__ instanceof Observer) {
ob = value.__ob__
} else {
ob = new Observer(value)
}
return ob
}

vm.$watch

vm.$set

vm.$delete