观察者模式
什么是观察者模式
提示
观察者模式(Observer) 通常又被称为发布-订阅者模式或消息机制, 它定义了对象间的一种一对多的依赖关系, 只要当一个对象的状态发生改变时, 所有依赖于它的对象都得到通知并被自动更新, 解决了主体对象与观察者之间功能的耦合, 即一个对象状态改变给其他对象通知的问题。
下面这段代码就是一种发布-订阅模式:
javascript
document.querySelector('#btn').addEventListener('click', function () {
alert('You click this button');
}, false)
1
2
3
2
3
除了我们常见的 DOM 事件绑定外,观察者模式应用的范围还有很多~
比如 vue2 框架里不少地方都涉及到了观察者模式,比如:
数据的双向绑定
利用Object.defineProperty()对数据进行劫持, 设置一个监听器Observer,用来监听所有属性, 如果属性上发上变化了,就需要告诉订阅者Watcher去更新数据, 最后指令解析器Compile解析对应的指令, 进而会执行对应的更新函数,从而更新视图,实现了双向绑定。
子组件与父组件通信
Vue中我们通过props完成父组件向子组件传递数据, 子组件与父组件通信我们通过自定义事件即
创建一个观察者
首先我们需要创建一个观察者对象,它包含一个消息容器和三个方法,分别是:
- on:订阅消息
- off:取消订阅消息
- publish:发送订阅消息
javascript
const Observe = (function() {
// 防止消息队列暴露而被篡改,将消息容器设置为私有变量
let __message = {};
return {
// 注册消息接口
on: function(type, fn) {
// 如果此消息不存在,创建一个该消息类型
if (typeof __message[type] === 'undefined') {
// 将执行方法推入该消息对应的执行队列中
__message[type] = [fn];
} else {
// 如果此消息存在,直接将执行方法推入该消息对应的执行队列中
__message[type].push(fn);
}
},
// 发布消息接口
publish: function(type, args) {
// 如果该消息没有注册,直接返回
if (!__message[type]) return;
// 定义消息信息
const events = {
type, // 消息类型
args: args || {} // 参数
}
// 遍历执行函数
for (let i = 0, len = __message[type].length; i < len; i++) {
// 依次执行注册消息对应的方法
__message[type][i].call(this, events)
}
},
// 移除消息接口
off: function(type, fn) {
// 如果消息执行队列存在
if (__message[type] instanceof Array) {
// 从最后一条依次遍历(之所以从最后一个开始遍历,是因为下面会用到splice)
for (let i = __message[type].length - 1; i >= 0; i--) {
// 如果存在该执行函数则移除相应的动作
__message[type][i] === fn && __message[type].splice(i, 1);
}
}
}
}
})();
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
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
使用:
javascript
// 订阅消息
Observe.on('say', function (data) {
console.log(data.args.text);
})
Observe.on('success',function () {
console.log('success')
});
//发布消息
Observe.publish('say', { text : 'hello world' } )
Observe.publish('success');
1
2
3
4
5
6
7
8
9
10
11
2
3
4
5
6
7
8
9
10
11
我们在消息类型为say、success的消息中注册了两个方法,其中有一个接受参数,另一个不需要参数, 然后通过publish发布say和success消息,结果跟我们预期的一样, 控制台输出了 hello world 以及 success。
实现EventBus
javascript
class EventBus {
constructor() {
this.event = Object.create(null);
};
// 注册事件
on(name, fn) {
if(!this.event[name]){
// 一个事件可能有多个监听者
this.event[name]=[];
};
this.event[name].push(fn);
};
// 触发事件
emit(name, ...args) {
//给回调函数传参
this.event[name] && this.event[name].forEach(fn => {
fn(...args)
});
};
// 只被触发一次的事件
once(name, fn) {
// 在这里同时完成了对该事件的注册、对该事件的触发,并在最后取消该事件。
const cb=(...args)=>{
//触发
fn(...args);
//取消
this.off(name,fn);
};
//监听
this.on(name, cb);
};
// 取消事件
off(name, offCb) {
if(this.event[name]){
const index = this.event[name].findIndex((fn)=>{
return offCb === fn;
})
if (index !== -1) {
this.event[name].splice(index, 1);
if(!this.event[name].length){
delete this.event[name];
}
}
}
}
}
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
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