发布订阅
发布订阅是一种设计模式。
发布订阅:
一个对象能够发布一个事件,然后你能订阅这个事件。
设计模式:
是一种可用的、别人用过觉得还行的一种写代码套路,比如用闭包去隐藏变量。
需求:
两个模块间形成事件交互。
模块A和B ,中间值e叫做事件中心。
A有什么事通知B就触发一下e,B就订阅一下。
B有什么事通知A就出发一下e,A就订阅一下
两者不直接交流,由e牵线搭桥。
分析
我们需要实现一个对象,这个对象就是通信的中介eventHub,实现三个接口。
eventHub这个对象要有三个函数
const eventHub = {
on:()=>{} //监听事件
emit:()=>{} //trigger 触发事件
off:()=>{} //取消监听事件
}
//在任意地方使用on来监听事件
eventHub.on('click',f1)
//用off取消监听事件
eventHub.off('click',f1)
//可以在用户点击按钮 或三秒钟之后触发它
setTimeout(()=>{
eventHub.emit('click','frank')
},3000)
实现
写函数首先搞清楚输入输出是什么
on 接收两个参数 事件类型和函数,不需要返回值。
off同理。
emit接收事件类型和参数。依旧不需要返回值
它们分别做什么
on:如果你监听了一个事件,我就把你放到任务队列中。
off; 就是把它从任务队列中拿出来。
emit就是调用队列中的任务。
任务队列是什么
- 任务:函数就是任务。
- 队列:
需要在触发时执行这个任务,比如我监听两次click。
eventHub.on('click',f1)
eventHub.on('click',f2)
那我f1 和 f2调用的顺序,先调用f1再调用f2。
先进先出就是队列,所以我们需要一个队列。
但是实际上我们会有很多队列:如click队列,右键队列 左键多列等等。
所以我们要实现一个映射关系,用map 叫哈希表。JS中只能用对象来表示哈希表。
所以我们实现一个map队列表。
用到的其他东西
- 防御性编程:如果name不存在我们可以初始化一个。
on:(name,fn)=>{
写法1:直接push
eventHub.map[name].push(fn)可以 但是需要防御性编程 name有可能是空的
写法2:防御性编程
if(eventHub.map[name]===undefined){
eventHub.map[name] = []
}
eventHub.map[name].push(fn)
写法3:简写
eventHub.map[name] = eventHub.map[name] || []
eventHub.map[name].push(fn)
//这里不能乱用别名,因为另外函数是这个是写。q = []就出现问题了
}
- 短路:可以少写else
if(!q){return}
if(index<0) {return}
- 别名:减少代码。alias设计模式,叫缩写也叫别名。
const q = eventHub.map[name]
-
forEach和map:
用map和forEach遍历基本一样 但是map有返回值 forEach没有 - 代码
const eventHub = {
map:{
},
on:(name,fn)=>{
//入队
eventHub.map[name] = eventHub.map[name] || []
eventHub.map[name].push(fn)
} ,
emit:(name,data)=>{
const q = eventHub.map[name]
if(!q){return}
q.map((f)=>{f.call(null,data)})//遍历调用一遍。 这f的this是空。
} ,
off:(name,fn)=>{
const q = eventHub.map[name]
if(!q){return}
const index = q.indexOf(fn)
if(index<0) {return}
q.splice(index,1)
}
}
- 测试
eventHub.on('click',console.log)
eventHub.on('click',console.error)
setTimeout(()=>{
eventHub.emit('click','frank')
},3000)
-
完美
发布订阅
面试有可能会问到 如何实现once:()=>{}
只监听一次,自动结束。