JS设计模式大合集

仓库地址:JS设计模式大合集
欢迎大家 Star ,一起交流学习!

JS设计模式-发布订阅者模式

如果你用过VUE、React, 你应该对这个发布者订阅者模式不陌生,明天让我们来一起来探讨一下发布订阅者模式。

什么是发布订阅者模式

在我们日常生活中,其实有很多发布订阅者模式案例。

  • 我们在微信里面订阅了一个公众号,这个公众号 在某一个时间阶段会给我发推文。

  • 在电商平台预约一个商品,等商品有货时,平台会给我们发短信通知。

  • 点一个外卖。
    ……

等等数不胜数。

不过从上面的例子,我们可以 提取出三样东西, 一个是订阅者,一个是平台,一个是发布者

订阅者会向平台订阅属于自己的个性需求, 平台则会收集这个需求, 发布者就会根据这个需求发布对应的内容, 在此之后,平台会把发布者发布的内容推送到订阅者。

好了,有了这三个基本接下来我们可以开始创造属于我们自己的发布订阅者模式啦。

创建一个发布订阅模式

首先我们需要一个平台,它可以记录订阅者的需求,以及让发布者能够根据需求发布的功能:

1
2
3
const platform = {
eventPool:{}, // 事件中心,用于存储订阅者下的订单以及对应订单的回调函数
}

有了平台之后,我们需要给订阅者提供订阅事件的功能:

1
2
3
4
5
6
7
8
9
10
const platform = {
eventPool:{}, // 事件中心,用于存储订阅者下的订单以及对应订单的回调函数
// 订阅事件
on: function(eventName, callback) {
if(!this.eventPool[eventName]) {
this.eventPool[eventName] = []; // 一个事件下,可能需要多个任务,所以我们用一个数组来记录
}
this.eventPool[eventName].push(callback);
},
}

同时,我们也要给发布者提供发布事件的功能:

1
2
3
4
5
6
7
8
9
10
11
12
const platform = {
eventPool:{}, // 事件中心,用于存储订阅者下的订单以及对应订单的回调函数
// 发布事件
emit: function (eventName, data) {
if (this.eventPool[eventName]) {
this.eventPool[eventName].forEach(callback => {
callback(data); // 因为一个事件可能有很多对应的任务,我们之前是用数组存储的,所以这里都要执行
});
}
return; // 如果没有对应的事件名称,则返回
},
}

取消事件监听

虽然我们事件是成功创建了,但是,如果我们不需要对应的事件了,或者需要对事件进行限制 节省资源,所以我们需要对已经不在重要的事件进行移除。

1
2
3
4
5
6
7
8
9
10
const platform = {
eventPool: {}, // 事件中心,用于存储订阅者下的订单以及对应订单的回调函数
// 移除事件
off: function (eventName) {
if (this.eventPool[eventName]) {
delete this.eventPool[eventName];
}
return; // 如果没有对应的事件名称,则返回
}
};

好了,我们把整体拼装起来,并且,也来试一试,是否可以成功进行订阅和发布。

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
const platform = {
eventPool: {}, // 事件中心,用于存储订阅者下的订单以及对应订单的回调函数
// 订阅事件
on: function (eventName, callback) {
if (!this.eventPool[eventName]) {
this.eventPool[eventName] = []; // 一个事件下,可能需要多个任务,所以我们用一个数组来记录
}
this.eventPool[eventName].push(callback);
},
// 发布事件
emit: function (eventName, data) {
if (this.eventPool[eventName]) {
this.eventPool[eventName].forEach(callback => {
callback(data); // 因为一个事件可能有很多对应的任务,我们之前是用数组存储的,所以这里都要执行
});
}
return; // 如果没有对应的事件名称,则返回
},
// 移除事件
off: function (eventName) {
if (this.eventPool[eventName]) {
delete this.eventPool[eventName];
}
return; // 如果没有对应的事件名称,则返回
}
};

// 创建订阅者
platform.on(`event1`, data1 => {
console.log('我是event1的回调函数1,他的接受到的数据是:', data1);
});
platform.on(`event1`, data2 => {
console.log('我是event1的回调函数2,他的接受到的数据是:', data2 + 2);
});
platform.on(`event2`, data3 => {
console.log('我是event2的回调函数3,他的接受到的数据是:', data3 + '0v0');
});
// 发布者执行任务
platform.emit(`event1`, `chuyuxuan`);
platform.off(`event1`);
platform.emit(`event1`, `chuyuxuan`);
platform.emit(`event2`, `chuyuxuan`);

/** 结果如下
* 我是event1的回调函数1,他的接受到的数据是: chuyuxuan
* 我是event1的回调函数2,他的接受到的数据是: chuyuxuan2
* 我是event2的回调函数3,他的接受到的数据是: chuyuxuan0v0
*/

可以看到,我们的发布,订阅,和取消事件都完美运行。

那么,是不是到这里就已经是完美了嘛?还有优化空间嘛?当然还有。

优化代码

从上面代码可以看到,发布订阅者模式利用到的是一个事件池对象,那是采取类似key-value格式,那么我们可以借助ES6的Map()对它进行处理。同时,对于事件来说,更像是一个类,我们也可以用ES6的class来进一步优化,这样,我们更加借助面相对象思想,使得代码更加易读和维护:

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
class Platform {
constructor() {
this.eventPool = new Map();
}

// 订阅事件
on(eventName, callback) {
if (typeof callback !== 'function') {
throw new Error('回调必须是一个函数');
}

const callbacks = this.eventPool.get(eventName) || [];
callbacks.push(callback);
this.eventPool.set(eventName, callbacks);
}

// 发布事件
emit(eventName, data) {
const callbacks = this.eventPool.get(eventName);
if (callbacks) {
callbacks.forEach(callback => {
callback(data);
});
}
}

// 移除事件
off(eventName, callback) {
if (this.eventPool.has(eventName)) {
if (callback) {
const callbacks = this.eventPool.get(eventName).filter(cb => cb !== callback);
this.eventPool.set(eventName, callbacks);
} else {
this.eventPool.delete(eventName);
}
}
}
}

// 创建平台实例
const platform = new Platform();

// 创建订阅者
platform.on('event1', data1 => {
console.log('订阅者 1:', data1);
});

platform.on('event1', data2 => {
console.log('订阅者 2:', data2 + 2);
});

platform.on('event2', data3 => {
console.log('订阅者 3:', data3 + '0v0');
});

// 发布事件
platform.emit('event1', 'chuyuxuan');
platform.off('event1');
platform.emit('event1', 'chuyuxuan'); // 这不应该触发任何回调,因为 'event1' 已取消订阅
platform.emit('event2', 'chuyuxuan');

结束语

到这,我们对于发布订阅者模式的理解和代码编辑就差不多啦,如果还想继续,我们可以从这些方面来考虑:

  • 错误处理:虽然我们已经添加了一些简单的错误处理,但在真实的应用中可能需要更复杂的错误处理机制,例如处理回调函数执行时可能出现的异常。

  • 异步事件处理:如果你的应用需要处理异步事件,可能需要在订阅和发布事件的方法中添加异步处理逻辑,例如使用 Promise 或 async/await。

  • 性能优化:在处理大量事件或者大量订阅者时,可能需要考虑性能优化,例如使用更高效的数据结构或算法。

  • 跨组件通信:如果你的应用是一个复杂的前端应用,可能需要考虑跨组件通信的问题,例如在不同组件之间发布和订阅事件。

  • 内存泄漏防护:如果订阅者忘记取消订阅事件,可能会导致内存泄漏问题,因此可能需要一些机制来自动取消订阅,或者提醒开发者及时取消订阅。

  • 安全性考虑:如果你的应用涉及敏感信息或者用户身份验证等安全性问题,可能需要考虑如何确保发布订阅模式的安全性,例如防止事件被未经授权的订阅者订阅。