简单实现一个EventEmiter
发布-订阅设计模式对大家来说并不是很陌生,举一个最简单的例子,在前端开发过程中,事件的绑定就是其实际的应用。首先我们先了解下什么是发布-订阅模式。
基本概念:发布-订阅模式又叫观察者模式,它定义对象间的一种一对多的依赖关系,当一个对象的状态发生改变时,所有依赖它的对象都得到通知。在前端开发中,我们一般用事件模型来替代传统的发布-订阅模式。
发布-订阅模式是前端常用的一种设计模式,现在主流的MVVM框架,都大量使用了此设计模式,其主要作用有以下两点:一是可以实现模块间通信,而是可以在一定程度上实现异步编程。其基本事件模型如下:

前端的事件绑定有三要素,一是传入事件类型,二是声明对应的回调方法,三是触发条件;触发条件为对应的事件类型。前端DOM的事件系统本质也是发布-订阅模式,而我们在业务处理中所应有的模式也与此类似,只不过发布订阅模式应用的是自定义事件类型,可以自定义。
发布订阅事件模型本质上就是一个中央事件处理总线,它接收所有订阅的自定义事件,并将自定义事件的回调存储到事件回调的堆栈中,当某在某个时刻触发了此自定义事件,会调用分发事件的方法,从事件回调堆栈中取出符合条件的事件回调,依次调用,具体实现逻辑如下:
class EventEmiter {
constructor() {
//回调中心
this.listenerList = {};
this.createEventList = {};
}
/**
* 添加事件监听
* @param {事件类型} type
* @param {回调方法} fn
*/
on(type, fn) {
if (!this.listenerList[type]) {
this.listenerList[type] = [];
}
this.listenerList[type].push(fn);
}
/**
* 触发事件监听
* @param {事件类型} type
*/
emit(type, flag) {
let fnList = this.listenerList[type];
let params = Array.from(arguments);
if (!fnList || fnList.length === 0) {
return false;
} else {
fnList.forEach(fn => {
fn.apply(this, params.slice(1));
});
}
}
/**
* 移除事件监听
* @param {事件类型} type
* @param {回调方法} fn
*/
off(type, fn) {
if (!this.listenerList[type]) {
return false;
} else {
let index = this.listenerList[type].findIndex(vv => vv === fn);
if (index === -1) {
return false;
} else {
this.listenerList[type].splice(index, 1);
}
}
}
}
let eventBus = new EventEmiter();
function cb(param) {
console.log("this is a test", param);
}
eventBus.on("test", cb);
eventBus.emit("test", 123);
eventBus.off("test", cb);
eventBus.emit("test", 456);
以上只是对发布订阅模式进行一个简单的实现,自定义事件只能分发给在触发前已订阅的消息,针对那些先触发,后订阅的内容,并不能得到一个很好的处理,所以,如果要解决这种弊端,就必须加一个离线的事件缓存。除此之外,发布订阅也有一些弊端,那就是每次发布消息,都会触发所有的事件监听回调,尽管大多数情况下并不想触发所有回调内容,所以在这种情况下,最好对事件加一些命名空间,以缩小其生效范围。
以下为支持离线事件代码,只是对事件加了一个标记:
class EventEmitter {
constructor() {
//回调中心
this.listenerMap = {};
//离线事件列表
this.offlineListenerList = [];
}
/**
* 添加事件监听
* @param type 事件类型
* @param fn 回调函数
* @param flag 是否是离线事件
*/
on(type, fn, flag) {
if (!this.listenerMap[type]) {
this.listenerMap[type] = [];
}
this.listenerMap[type].push(fn);
//如果注册了离线事件,则在监听事件时,需要检测是否有离线事件缓存
if (flag) {
let index = this.offlineListenerList.findIndex(vv => vv.type === type);
if (index !== -1) {
fn.call(this, this.offlineListenerList[index].params);
//清空该条离线事件记录
this.offlineListenerList.splice(index, 1);
}
}
}
/**
* 触发事件监听
* @param type 事件类型
* @param params 载荷参数
* @param flag
*/
emit(type, params, flag) {
let fnList = this.listenerMap[type];
if (fnList && Array.isArray(fnList)) {
fnList.forEach(fn => {
fn.apply(this, params);
});
}
//如果注册的是离线事件,则吧
if (flag) {
this.offlineListenerList.push({
type,
params
});
}
}
/**
* 移除事件监听
*/
off(type, fn) {
if (!this.listenerMap[type]) {
return false;
} else {
let index = this.listenerMap[type].findIndex(vv => vv === fn);
if (index === -1) {
return false;
} else {
this.listenerMap[type].splice(index, 1);
}
}
}
/**
* 只触发一次
* @param type
* @param fn
*/
once(type, fn) {
let fnList = this.listenerMap[type];
let params = Array.from(arguments);
if (!fnList || fnList.length === 0) {
return false;
} else {
let index = fnList.findIndex( vv => vv === fn);
fnList[index].apply(this, params.slice(1));
fnList.splice(index, 1);
}
}
}
let event = new EventEmitter();
event.emit('test', 1, true);
event.on('test', params => {
console.log('offline', params)
}, true);
event.on('cc', () => {
console.log('normal', 22222);
});
event.emit('cc');
简单实现一个EventEmiter的更多相关文章
- 最简单的一个Oracle定时任务
最简单的一个Oracle定时任务一.在PLSQL中创建表:create table HWQY.TEST(CARNO VARCHAR2(30),CARINFOID NUMBER) 二.在PLSQ ...
- 在浏览器中简单输入一个网址,解密其后发生的一切(http请求的详细过程)
在浏览器中简单输入一个网址,解密其后发生的一切(http请求的详细过程) 原文链接:http://www.360doc.com/content/14/1117/10/16948208_42571794 ...
- shell中,我们可以通过简单的一个判断来判断命令是否存在
shell中,我们可以通过简单的一个判断来判断命令是否存在 which "Command" > /dev/null if [ $? -eq 0 ] then echo com ...
- 编写函数求整形数组a中存储的m个不重复的整数的第k大的整数(其中m>=1,1<=k<=m)很简单的一个思路是酱紫的:管他辣么多干啥,上来一把排序然后直接得答案
/** * @author:(LiberHome) * @date:Created in 2019/2/28 20:38 * @description: * @version:$ *//*编写函数求整 ...
- Blender简单动画:一个小球从一座山上滚下.
简单动画:一个小球从一座山上滚下.注:[key]方括号内是快捷键; {大括号}内是模式,页签名称或选项等. ==== 1. 建模: == 1.1 山[shift A] 建立平面plane,可以大 ...
- [k8s]简单启动一个k8s集群
简单启动一个k8s集群 kube-master mkdir -p /root/logs/api-audit /root/logs/controller /root/logs/scheduler kub ...
- Vue - 简单实现一个命令式弹窗组件
前言 在日常工作中弹窗组件是很常用的组件,但用得多还是别人的,空闲时间就自己来简单实现一个弹窗组件 涉及知识点:extend.$mount.$el 使用方式: this.$Confirm({ titl ...
- 一个简单的一个sql表遍历
简单的一个sql表遍历 一般我们写储存过程或者其他sql语句的时候都会用到循环遍历数据,最常用的两种就是 1.游标 2.临时表+while 下面贴出示例代码 DECLARE @MinReLogID I ...
- MY SQL数据库密码最简单的一个方法()
https://zhidao.baidu.com/question/564368111.html 非常简单的一个修改方法!!!!!!!!!!!!!!!!!!!!! 最简单的方法就是借助第三方工具Nav ...
随机推荐
- 【模板】MST(Prim)
代码如下 #include <bits/stdc++.h> using namespace std; const int maxv=2e5+10; const int maxe=5e5+1 ...
- ASP.NET MVC —— Model之一模型模板
http://www.cnblogs.com/lzhp/archive/2013/03/25/2981650.html Mvc model系列文章主要分为三部分:Model Templates,Mod ...
- 以Java的视角来聊聊BIO、NIO与AIO的区别?
转: 以Java的视角来聊聊BIO.NIO与AIO的区别? 飞丫玲丫 17-07-2623:10 题目:说一下BIO/AIO/NIO 有什么区别?及异步模式的用途和意义? BIO(Blocking I ...
- std::bind常见的坑
http://note.youdao.com/noteshare?id=bce9cdea8e94501186b5ba3026af685f
- P3959 宝藏
P3959 宝藏 题目描述 参与考古挖掘的小明得到了一份藏宝图,藏宝图上标出了 nn 个深埋在地下的宝藏屋, 也给出了这 nn 个宝藏屋之间可供开发的 mm 条道路和它们的长度. 小明决心亲自前往挖掘 ...
- [iOS]Xcode处理过时方法的警告
####强迫症的福利, 有的时候, 我们特别讨厌Xcode中的代码警告, 以下就是遇到各种警告的时候的处理方法:(后续会一直更新) 产生警告的原因: 某些方法废弃了, 会产生警告! 样式: 处理方法: ...
- 【NOI】2017 蚯蚓排队(BZOJ 4943,LOJ 2303) 模拟+hash
[题目]#2303. 「NOI2017」蚯蚓排队 [题意]给定n条长度不超过6的蚯蚓,初始各自在一个队伍.m次操作:1.将i号蚯蚓和j号蚯蚓的队伍合并(保证i为队尾,j为队首).2.将i号蚯蚓和它后面 ...
- java concurrent 中ExecutorService和CompletionService简单区别
举个例子,现在需要执行10个任务,这些任务都是有返回值,并且需要使用10个线程同时执行.一般的做法就是创建ExecutorService线程池,pool大小10,每个任务实现Callable接口,然后 ...
- 第7月第19天 swift on linux
1. https://github.com/iachievedit/moreswift http://dev.iachieved.it/iachievedit/more-swift-on-linux/ ...
- retrying模块的学习
retrying模块的学习 我们在写爬虫的过程中,经常遇到爬取失败的情况,这个时候我们一般会通过try块去进行重试,但是每次都写那么一堆try块,真的是太麻烦,所以今天就来说一个比较pythonic的 ...