简单实现一个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 ...
随机推荐
- Python基础学习(二)
前一段时间学习了Python数据类型,语句和函数,目前书写python的新特性,继续练手!!!! 一.切片 之前我们从python的list 或者 tuple中取得元素都是这样写,显然不够灵活 lis ...
- python数字前自动补零
>>> '%d' % 23 #输出23 ' >>> '%5d' % 23 #输出的数字前有3个空位,共占5个字符 ' >>> '%05d' % 2 ...
- python---django中form组件(2)自定制属性以及表单的各种验证,以及数据源的实时更新,以及和数据库关联使用ModelForm和元类
自定义属性以及各种验证 分析widget: class TestForm(forms.Form): user = fields.CharField( required = True, widget = ...
- WHAT I READ FOR DEEP-LEARNING
WHAT I READ FOR DEEP-LEARNING Today, I spent some time on two new papers proposing a new way of trai ...
- python assert 断言语句的作用
python assert 断言语句的作用 assert语句的应用场景 使用assert语句是一个很好的习惯. 我们在编写代码的时候, 不知道程序会在什么时候崩溃, 与其让它在深度运行时崩溃, 不如预 ...
- 关于Web安全的那些事(XSS攻击)
概述 XSS攻击是Web攻击中最常见的攻击方法之一,它是通过对网页注入可执行代码且成功地被浏览器执行,达到攻击的目的,形成了一次有效XSS攻击,一旦攻击成功,它可以获取用户的联系人列表,然后向联系人发 ...
- [原]Android Fragment 入门介绍
Fragment Fragment 产生,优点,用途,使用方法简介 1 Fragmeng简介 自从Android 3.0中引入fragments 的概念,根据词海的翻译可以译为:碎片.片段.其上的是为 ...
- AngularJS入门基础——表单验证
<form name="form" novalidata> <label name="email">your email</l ...
- Dream------Hadoop--网络拓扑与Hadoop--摘抄
两个节点在一个本地网络中被称为“彼此的近邻”是什么意思?在高容量数据处理中,限制因素是我们在节点间 传送数据的速率-----带宽很稀缺.这个想法便是将两个节点间的带宽作为距离的衡量标准. 衡量节点 ...
- Linux中普通用户提权为超级用户
首先创建一个普通用户,并且给普通用户设置一个密码,保证能用su 命令能用普通用户登录 [root@ahu ~]# useradd test [root@ahu ~]# passwd test New ...