从观察者模式和发布-订阅模式到Vue响应式原理

我们知道Vue的响应式原理用到了发布-订阅模式,之前一直分不清观察者模式和发布-订阅模式之前的区别。

观察者模式和发布订阅模式最大的区别就是发布订阅模式有个事件调度中心。

观察者模式和发布订阅模式

观察者模式

一个目标对象管理所有依赖于它的观察者对象,并且在它本身的状态改变时主要发出通知。

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
function Subject(){
this.observers = []
}

Subject.prototype = {
constructor: Subject,
// 注册
add: function(observer){
this.observers.push(observer)
},
// 发送通知
notify: function(context){
this.observers.forEach(observer=>{
observer.update(context)
})
},

// 取消注册
remove: function(observer){
this.observers.splice(this.observers.indexOf(observer),1)
}

}

// 观察者A
function ObserverA(){
this.update = function(context){
console.log("A update:" + context)
}
}

// 观察者B
function ObserverB(){
this.update = function(context){
console.log("B update:" + context)
}
}

// 创建观察者实例a、b
var a = new ObserverA()
var b = new ObserverB()

// 创建目标实例subject
var subject = new Subject()

// 将观察者a b添加到目标中
subject.add(a)
subject.add(b)

subject.notify("data change")
//"A update:data change"
//"B update:data change"

发布订阅模式

发布订阅有两个参与者:发布者和订阅者。发布者向某个信道发送一条消息,订阅者绑定这个信道,当有消息发布至信道就会接收到一个通知。

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
// 调度中心
class PubSub {
constructor(){
this.subscribers = {}
}
subscribe(type, fn){
if(!Object.prototype.hasOwnProperty.call(this.subscribers, type)){
this.subscribers[type] = []
}
this.subscribers[type].push(fn)
}
publish(type, ...args){
let listeners = this.subscribers[type]
if(!listeners || !listeners.length) return;

listeners.forEach(fn=>{
fn(...args)
})
}

unsubscribe(type, fn){
let listeners = this.subscribers[type]
if(!listeners || !listeners.length) return;

this.this.subscribers[type] = listeners.filter(v => v !== fn )
}
}

let ob = new PubSub()

ob.subscribe('add',(val) => {console.log(val)})

ob.publish('add', 1) // 1

可以看出发布订阅是完全解耦的;而在观察者模式中,目标对象管理观察者对象,双方是耦合的。

Vue 响应式系统

vue的数据初始化

1
2
3
4
5
6
7
var v = new Vue({
data(){
return {
a: 'hello'
}
}
})

官网上的图:

vue响应式系统

图源自@xuqiang521
vue响应式原理

数据劫持

从上图可以看到,数据劫持的核心方法使用的就是Object.defineProperty()把属性转换为getter/setter。在数据变更的时候,会进入到封装的DepWatch中进行处理。

遍历劫持

基本类型和对象的处理

1
2
3
4
5
6
walk(obj){
const keys = Object.keys()
for(let i = 0;i < keys.length; i++){
defineReactive(obj, keys[i], obj[keys[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
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

export default defineReactive(
obj: object,
key: string,
val: any,
customSetter?: Function
){
// 在闭包中定义一个Dep对象
const dep = new Dep()

// 获取属性描述符 查看属性是否可以配置
const property = Object.getOwnPropertyDescriptor(obj, key)
if (property && property.configurable === false) {
return
}

// 如果之前属性已经预设了getter/setter函数,则将他们取出来,新定义的getter/setter函数会将其执行,保证不会覆盖之前定义的getter/setter函数
const getter = property && property.get
const setter = property && property.set

// 对象的子对象递归进行observe并返回子节点的Observer对象
let childOb = observe(val)
Object.defineProperty(obj, key, {
enumerable: true,
configurable: true,
get: function reactiveGetter(){
// 如果属性有getter则执行
var 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){
// 通过getter获取当前值,与新值进行比较,如果一样则返回
const value = getter ? getter.call(obj) : val
if(value === newVal || (newVal !== newVal && value !== value)){
return
}
/* eslint-enable no-self-compare */
if (process.env.NODE_ENV !== 'production' && customSetter) {
customSetter()
}
// 如果原本有setter方法则执行setter方法
if(setter){
setter.call(obj, newVal)
} else {
val = newVal
}

// 新的值需要重新observe,已保证数据的响应式
childOb = observe(newVal)

// dep对象通知所有订阅者
dep.notify()
}
})


}

###

image.png