发布-订阅设计模式对大家来说并不是很陌生,举一个最简单的例子,在前端开发过程中,事件的绑定就是其实际的应用。首先我们先了解下什么是发布-订阅模式。

    基本概念:发布-订阅模式又叫观察者模式,它定义对象间的一种一对多的依赖关系,当一个对象的状态发生改变时,所有依赖它的对象都得到通知。在前端开发中,我们一般用事件模型来替代传统的发布-订阅模式。

  发布-订阅模式是前端常用的一种设计模式,现在主流的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的更多相关文章

  1. 最简单的一个Oracle定时任务

    最简单的一个Oracle定时任务一.在PLSQL中创建表:create table HWQY.TEST(CARNO     VARCHAR2(30),CARINFOID NUMBER) 二.在PLSQ ...

  2. 在浏览器中简单输入一个网址,解密其后发生的一切(http请求的详细过程)

    在浏览器中简单输入一个网址,解密其后发生的一切(http请求的详细过程) 原文链接:http://www.360doc.com/content/14/1117/10/16948208_42571794 ...

  3. shell中,我们可以通过简单的一个判断来判断命令是否存在

    shell中,我们可以通过简单的一个判断来判断命令是否存在 which "Command" > /dev/null if [ $? -eq 0 ] then echo com ...

  4. 编写函数求整形数组a中存储的m个不重复的整数的第k大的整数(其中m>=1,1<=k<=m)很简单的一个思路是酱紫的:管他辣么多干啥,上来一把排序然后直接得答案

    /** * @author:(LiberHome) * @date:Created in 2019/2/28 20:38 * @description: * @version:$ *//*编写函数求整 ...

  5. Blender简单动画:一个小球从一座山上滚下.

    简单动画:一个小球从一座山上滚下.注:[key]方括号内是快捷键; {大括号}内是模式,页签名称或选项等. ==== 1. 建模:    == 1.1 山[shift A] 建立平面plane,可以大 ...

  6. [k8s]简单启动一个k8s集群

    简单启动一个k8s集群 kube-master mkdir -p /root/logs/api-audit /root/logs/controller /root/logs/scheduler kub ...

  7. Vue - 简单实现一个命令式弹窗组件

    前言 在日常工作中弹窗组件是很常用的组件,但用得多还是别人的,空闲时间就自己来简单实现一个弹窗组件 涉及知识点:extend.$mount.$el 使用方式: this.$Confirm({ titl ...

  8. 一个简单的一个sql表遍历

    简单的一个sql表遍历 一般我们写储存过程或者其他sql语句的时候都会用到循环遍历数据,最常用的两种就是 1.游标 2.临时表+while 下面贴出示例代码 DECLARE @MinReLogID I ...

  9. MY SQL数据库密码最简单的一个方法()

    https://zhidao.baidu.com/question/564368111.html 非常简单的一个修改方法!!!!!!!!!!!!!!!!!!!!! 最简单的方法就是借助第三方工具Nav ...

随机推荐

  1. kubernetes配置secret拉取私仓镜像

    2017.05.10 19:48* 字数 390 阅读 5216评论 0喜欢 8 对于公司内部的项目, 我们不可能使用公有开放的镜像仓库, 一般情况可能会花钱买docker私仓服务, 或者说自己在服务 ...

  2. Java基础-Java中的内存分配与回收机制

    Java基础-Java中的内存分配与回收机制 作者:尹正杰 版权声明:原创作品,谢绝转载!否则将追究法律责任. 一. 二.

  3. OpenResty 扩展库(二)lua-resty-template

    Lua和OpenResty的模板引擎(HTML) 模板语法 您可以在模板中使用以下标签: {{expression}},写入表达式的结果 - html转义 {*expression*},写入表达结果 ...

  4. AngularJS入门基础——表达式

    表达式在AngularJS应用中广泛的使用,因此深入理解AngularJS如何使用并运算表达式是非常重要的.   表达式和eval非常相似,但是由于表达式由AngularJS来处理,它们有已下显著不同 ...

  5. shell 检测安装包

    检测 wget 是否存在 rpm -q wget >/dev/null ];then echo "install wget,Please wait..." yum -y in ...

  6. MFC里ON_COMMAND_RANGE消息映射的ID问题

    今天在工作中遇到一个问题,一个动态菜单,每个菜单的菜单项ID是我自己定义的,定义如下: #define IDM_SEARCHRECORD0 222240 #define IDM_SEARCHRECOR ...

  7. 【译】使用OpenVAS 9进行漏洞扫描

    本文译自Vulnerability Scanning with OpenVAS 9 part 1: Installation & Setup系列,本文将融合目前已经发表的四个部分. Part ...

  8. 阿里云OSS 中文名称地址不对

    oss中将该中文名称重命名.再输入一次

  9. linux下usb转串口驱动分析【转】

    转自:http://blog.csdn.net/txxm520/article/details/8934706 首先说一下linux的风格,个人理解 1. linux大小结构体其实是面向对象的方法,( ...

  10. Visual Studio 2013更新内容简介

    前言 VS2013终于发布了,虽然之前自己使用VS2010和VS2012的时间也不长,尤其是VS2012这自己刚刚也没用多久,看到VS2013发布了,自己忍不住也下载了下来,官网肯定可以下载,不过自己 ...