通信的中介
第一核心代码:原始
const eventHub = {
on: ()=>{},//
emit:()=>{},
off:()=>{}
}
eventHub.on('click',f1)//在任意时刻用on来监听一个事件;
eventHub.off('click',f1)//不想监听,监听click的时候,调用f1了。
setTimeout(()=>{
eventHub.emit('click','frank')
},3000)//3秒后在用户点击什么,来触发这个函数
复制代码
第二:关注输入和输出
- 写的是一个函数,只需要关注他的输入和输出了。name是事件的名字
- on接收两个参数,name,fn,输出值没人关心,就return undefined;
- off接收两个参数,name,fn,输出值没人关心,就return undefined;
- emit接收两个参数name,data,输出值没人关心,就return undefined;
const eventHub = {
on: (name,fn)=>{
return undefined
},
emit:(name.data)=>{
return undefined
},
off:(name,fn)=>{
return undefined
}
}
eventHub.on('click',f1)//在任意时刻用on来监听一个事件;
eventHub.off('click',f1)//不想监听,监听click的时候,调用f1了。
setTimeout(()=>{
eventHub.emit('click','frank')
},3000)//3秒后在用户点击什么,来触发这个函数
复制代码
上面就是核心的函数,已经写完了
实现功能
- on的功能是,如果监听一个事件,就把他放到任务队列里面;
- 任务就是函数,队列,就是监听事件时,有可能调用多个函数,先进先出。
- 先提供个
queue:[[]]
,不同的name,有不同的数组,叫映射,所以用map,此处就用queueMap:{},
- 映射就是用map,也就是哈希表,queuemap队列的表;
- 把on监听的事件放到map里面,
eventHub.map[name].push(fn)
,如果是空的就给他初始化一下,如果是空的,就让他等于初始化的队列吧 - `if(eventHub.map[name]===undefined){
eventHub.map[name] = []
}`
- 但上面这样写不好看,可以替换成下面
eventHub.map[name] = undefined || []
,如果为空就是后面的那个空数组。 - 初始化之后就是push
第三个步骤:on的时候添加具体事件
- 就是on的时候,找到一个对列,队列里面放一个任务;
const eventHub = {
map:{},
on: (name,fn)=>{
eventHub.map[name] = undefined || []
eventHub.map[name].push(fn)
},
复制代码
第4个步骤,off的应用
- on需要初始化,off不需要初始化,如果是空的直接返回
if(!eventHub.map[name]){return}
- 接着去找里面有没有这个函数;
const index = eventHub.map[name].indexOf(fn)
- 如果
if(index<0)
,就直接return; if (index>=0)
说明把fn添加到队列里面了,那就把它删掉;- 如何删除
eventHub.map[name].splice(index,1)
off:(name,fn)=>{
if(!eventHub.map[name]){return}
const index = eventHub.map[name].indexOf(fn)
if(index<0){return}
eventHub.map[name].splice(index,1)
}
复制代码
- 上面代码中
eventHub.map[name]
重复了太多次,用缩短法alias,用一个字母代替。q,最好只做读的操作,不做写的操作,所以在on里面不能用。
off:(name,fn)=>{
const q = eventHub.map[name]
if(!q){return}
const index = q.indexOf(fn)
if(index<0){return}
q.splice(index,1)
}
复制代码
第5步:emit的操作
- emit的作用就是把队列里面的内容调用一下;
- 首先找到这个队列;
const q = eventHub.mao[name]
- 如果里面的内容是空的,什么都不做
if(!q) return
,短路法; - 到了下一步,就证明这个q不为空了;
- 不为空就把Q遍历一下,forEach和map都可以遍历,指示map有返回值,q.map(f=>f(data));对里面每一个f进行调用;
- 写的时候可以这样q.map(f=>f.call(null,data))
- 首先找到这个队列;
emit:(name,data)=>{
const q = eventHub.map[name]
if(!q) return
q.map(f=>f.call(null,data))
return undefined
}
复制代码
来个大综合
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(undefined,data))
return undefined
},
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)//在任意时刻用on来监听一个事件;
eventHub.on('click',console.error)
setTimeout(()=>{
eventHub.emit('click','frank')
},3000)//3秒后在用户点击什么,来触发这个函数
复制代码
const eventHub = {
map: { // click: [f1 , f2] },
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)) return undefined
},
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)
复制代码
定义
发布-订阅模式其实是一种对象间一对多的依赖关系,当一个对象的状态发送改变时,所有依赖于它的对象都将得到状态改变的通知。 订阅者(Subscriber)把自己想订阅的事件注册(Subscribe)到调度中心(Event Channel),当发布者(Publisher)发布该事件(Publish Event)到调度中心,也就是该事件触发时,由调度中心统一调度(Fire Event)订阅者注册到调度中心的处理代码。
创建思路
-
创建一个
EventEmitter
类 -
在该类上创建一个事件中心(Map)
-
on
方法用来把函数 fn 都加到事件中心中(订阅者注册事件到调度中心) -
emit
方法取到 arguments 里第一个当做 event,根据 event 值去执行对应事件中心中的函数(发布者发布事件到调度中心,调度中心处理代码) -
off
方法可以根据 event 值取消订阅(取消订阅) -
once
方法只监听一次,调用完毕后删除缓存函数(订阅一次) -
注册一个
newListener
用于监听新的事件订阅
第一步,创建一个类,并初始化一个事件存储中心
class EventEmitter{
// 用来存放注册的事件与回调
constructor(){
this._events = {};
}
}
复制代码
第二步,实现事件的订阅方法 on
基本思路:将事件回调函数存储到对应的事件上
class EventEmitter{
// 用来存放注册的事件与回调
constructor(){
this._events = {};
}
on(eventName, callback){
// 由于一个事件可能注册多个回调函数,所以使用数组来存储事件队列
const callbacks = this._events[eventName] || [];
callbacks.push(callback);
this._events[eventName] = callbacks
}
}
复制代码
第三步,实现事件的发布方法 emit
基本思路:获取到事件对应的回调函数依次执行
class EventEmitter{
// 用来存放注册的事件与回调
constructor(){
this._events = {};
}
// args 用于收集发布事件时传递的参数
emit(eventName, ...args){
const callbacks = this._events[eventName] || [];
callbacks.forEach(cb => cb(...args))
}
}
复制代码
第四步,实现事件的取消订阅方法 off
基本思路:找到事件对应的回调函数,删除对应的回调函数
class EventEmitter{
// 用来存放注册的事件与回调
constructor(){
this._events = {};
}
off(eventName, callback){
const callbacks = this._events[eventName] || []
const newCallbacks = callbacks.filter(fn => fn != callback && fn.initialCallback != callback /* 用于once的取消订阅 */)
this._events[eventName] = newCallbacks;
}
}
复制代码
第五步,实现事件的单次订阅方法 once
基本思路: 1.先注册 2.事件执行后取消订阅
class EventEmitter{
// 用来存放注册的事件与回调
constructor(){
this._events = {};
}
//
once(eventName, callback){
// 由于需要在回调函数执行后,取消订阅当前事件,所以需要对传入的回调函数做一层包装,然后绑定包装后的函数
const one = (...args)=>{
// 执行回调函数
callback(...args)
// 取消订阅当前事件
this.off(eventName, one)
}
// 考虑:如果当前事件在未执行,被用户取消订阅,能否取消?
// 由于:我们订阅事件的时候,修改了原回调函数的引用,所以,用户触发 off 的时候不能找到对应的回调函数
// 所以,我们需要在当前函数与用户传入的回调函数做一个绑定,我们通过自定义属性来实现
one.initialCallback = callback;
this.on(eventName, one)
}
}
复制代码
复制代码
第六步,注册一个 newListener
用于监听新的事件订阅
基本思路:在用户注册的事件的时候,发布一下newListener事件
class EventEmitter{
// 用来存放注册的事件与回调
constructor(){
this._events = {};
}
on(eventName, callback){
// 如果绑定的事件不是newListener 就触发改回调
if(this._events[eventName]){
if(this.eventName !== "newListener"){
this.emit("newListener", eventName)
}
}
// 由于一个事件可能注册多个回调函数,所以使用数组来存储事件队列
const callbacks = this._events[eventName] || [];
callbacks.push(callback);
this._events[eventName] = callbacks
}
}
复制代码
复制代码
测试用例
const events = new EventEmitter()
events.on("newListener", function(eventName){
console.log(`eventName`, eventName)
})
events.on("hello", function(){
console.log("hello");
})
let cb = function(){
console.log('cb');
}
events.on("hello", cb)
events.off("hello", cb)
function once(){
console.log("once");
}
events.once("hello", once)
events.off("hello", once)
events.emit("hello")
events.emit("hello")
复制代码
复制代码
完整的代码
class EventEmitter{
constructor(){
this._events = {};
}
on(eventName, callback){
if(this._events[eventName]){
if(this.eventName !== "newListener"){
this.emit("newListener", eventName)
}
}
const callbacks = this._events[eventName] || [];
callbacks.push(callback);
this._events[eventName] = callbacks
}
emit(eventName, ...args){
const callbacks = this._events[eventName] || [];
callbacks.forEach(cb => cb(...args))
}
once(eventName, callback){
const one = (...args)=>{
callback(...args)
this.off(eventName, one)
}
one.initialCallback = callback;
this.on(eventName, one)
}
off(eventName, callback){
const callbacks = this._events[eventName] || []
const newCallbacks = callbacks.filter(fn => fn != callback && fn.initialCallback != callback /* 用于once的取消订阅 */)
this._events[eventName] = newCallbacks;
}
}
复制代码
© 版权声明
文章版权归作者所有,未经允许请勿转载。
THE END