✍️ Tangxt | ⏳ 2020-08-05 | 🏷️ 设计模式 |
从服务器获取数据:
元素监听用户行为:
透过事件池机制,来让你了解什么是「发布订阅设计模式」
我们使用 jQuery 的 ajax 方法,来发送 ajax 请求:
$.ajax({
url: '/api/v1/userlist',
success: function(result) {
fn1(result)
fn2(result)
fn3(result)
}
})
function fn1() {}
function fn2() {}
function fn3() {}
也可以使用 axios:
axios.get('/api/v1/userlist')
.then(res => {})
.catch((err) => {})
也可以使用发布订阅设计模式:
先规划好拿到数据之后,应该要做什么:
把 callback 扔到一个容器里边去 -> $on(fn1)
、$on(fn2)
-> 也可以移除 callback($off
)
数据拿到之后,直接就这样:
$.ajax({
url: '/api/v1/userlist',
success: function(result) {
// 依次执行容器里的方法
$emit(result)
}
})
这种姿势比传统的$.ajax()
要好!
发布订阅设计模式的思想是:
事先把要干的事给计划好(把
callback
扔到一个池子里去),当到达指定条件的时候(ajax
请求成功,响应回来数据)或者说某种信号来了,就依次执行容器里的方法!
这同事件池机制一样,监听事件,信号来了,依次执行事件池里的callback
补充:
观察者模式 vs 发布订阅模式:
有人这样理解它们二者的区别:
在我看来:
双向绑定的效果可以理解成观察者模式,单向数据流的效果可以理解成发布订阅模式
其实发布者就是资源的生产者,而订阅者就是需要资源的人(也就是消费者),我们是线下面对面交易?还是网上交易?
认识它,你得知道这么几个概念:
callback
) -> 自定义事件池($pond
) -> 也就是自定义的一个容器 -> 注意这可不是浏览器内置的事件哈!$on()
),买家还可以退货(移除某个信号的callback
,$off()
)$emit()/fire()
,为何发布?因为「发布者」把东西生产出来了)jQuery 是
on/off/fire
在真实项目里边的封装(类库、插件、组件)基本上都是基于面向对象的思想
this
是实例,那么这样各个方法中就可以实现信息的通信了!注意 ES7 提供了不用在
constructor
里写自有属性的姿势,还有透过装饰器,把一些工具方法注入到class
的原型里边去,以此来实现方法的复用
一个我不知道的点,可以直接这样添加原型方法:
(function(){
class EventBus(){
constructor(){
this.pond()
}
$on(type,func){}
$off(type,func){}
$emit(type,...args){}
}
// 可以这样添加原型方法,就像是构造函数一样……
EventBus.prototype.hi = function hi() {
console.log('hi')
}
window.EB = new EventBus();
})()
老师并没有弄三个 class 出来,直接就是用三个 API 来代表三种操作!
Sub
-> 定义原型方法,也就是定义「API 接口」 -> $pond、$on、$off、$emit
-> 这些 API 的设计是仿照 Vue 的 eventBusnew Sub
-> 使用注意,
splice
会导致数组塌陷的问题,这一点在定义$off
尤其需要注意!
代码的结构:
class Sub {
// 创建一个事件池
$pond = {}
// 向事件池中追加方法
$on(type,func) {}
// 从事件池中移除方法
$off(type,func){}
// 通知事件池中的方法执行
$emit(type,...params){}
}
去重的操作是在「向事件池中追加方法」时做的!
暴露给全局用的 API,有两种姿势:
第一种:暴露实例
window.subscribe = function subscribe() {
return new Sub
}
使用:
let sub = subscribe()
sub.$on('A',fn1)
sub.$on('A',fn2)
sub.$emit('A',1,2)
sub.$off('A',fn2)
第二种:暴露方法 API
let sub = new Sub
['$on','$off','$emit'].forEach(item => {
windowp[item] = function anonymous(...params) {
sub[item](...params)
}
});
使用:
$on('A',fn1)
$on('A',fn2)
$emit('A',1,2)
$off('A',fn2)
有很多种姿势可以实现 eventBus -> 你要关注的是其中的思想!
(function () {
class EventBus {
constructor() {
// 创建一个事件池 {xxx:[],...}
this.pond = {};
}
// 向事件池中追加方法
$on(type, func) {
// 每一次加方法的时候,首先看看事件池中是否存在这个类型,不存在就创建
let pond = this.pond;
!(type in pond) ? pond[type] = [] : null;
// 增加方法(去重)
let pondT = pond[type];
!pondT.includes(func) ? pondT.push(func) : null;
}
// 从事件池中移除方法
$off(type, func) {
let pondT = this.pond[type];
if (!pondT) return;
for (let i = 0; i < pondT.length; i++) {
let item = pondT[i];
if (item === func) {
// 移除掉(因为追加的时候去重了,所以删除一次就够了,不需要在向后找了);为了方式数组塌陷,我们此处不使用 pondT.splice(i, 1) 删除,我们先给其赋值为 null 即可
pondT[i] = null;
return;
}
}
}
// 通知事件池中某个类型对应的方法依次执行
$emit(type, ...args) {
let pondT = this.pond[type] || [];
for (let i = 0; i < pondT.length; i++) {
let func = pondT[i];
// 如果不是函数,在容器中移除掉
if (typeof func !== "function") {
pondT.splice(i, 1);
// 保证从 0 开始
i--;
continue;
}
func.apply(this, args);
}
}
}
window.EB = new EventBus();
})();
➹:第 23 题:介绍下观察者模式和订阅-发布模式的区别,各自适用于什么场景 · Issue #25 · Advanced-Frontend/Daily-Interview-Question
➹:Observer vs Pub-Sub pattern - Hacker Noon
➹:面试题, 实现一个 Event 类(发布订阅模式) - 知乎
addEventListener
、removeEventListener
on
(也可以从池子中移除 off
),当某个条件到达的时候(信号来了),我们通知池子中的方法依次执行 emit
…$emit/$on
subscribe
on/off/fire