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

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

  发布-订阅模式是前端常用的一种设计模式,现在主流的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. Python基础学习(三)

    了解了Python的基础使用,接下来继续练手廖雪峰老师的教学案例. 一.变量可以指向函数 说明,一个函数可以赋值给一个变量,该变量就会具有该函数的功能,举例: gg = abs print( gg(- ...

  2. 生成ssh-key for GIthub

    在Github里,如果我们想通过ssh的方式进行身份验证,我们就需要建立ssh-key: 方法一: git GUI,点击help,选择Generate ssh key

  3. 20181111 Quartz(慕课网)

    Quartz体系结构 三个核心概念 调度器 任务 触发器 重要组成 Job JobBuilder JobDetail JobStore Trigger TriggerBuilder ThreadPoo ...

  4. python---django中url访问方法

    只是了解,不推荐使用,毕竟干扰太多,任意冲突,也没有解耦,应该使用路由分发 在url匹配中支持正则匹配,例如: from django.conf.urls import include, urlfro ...

  5. HDU 3595 every-sg模型

    多个子游戏同时进行,每个子游戏给出两个数a,b,可以将大的数减去k倍小的数,不能操作者输. 策略就是对于一个必胜的游戏要使得步数更长,对于一个必败的游戏使得步数最短. 以下都来自贾志豪的论文.. 对于 ...

  6. 优雅地搭建整合ssm项目

    spring + spring mvc + mybatis 三大框架建议观看 黑马程序员出品的 Springmvc+Mybatis由浅入深全套视频教程 Spring框架2016版视频 观看顺序 ,我个 ...

  7. [整理]JS中的状态机

    /*StateMachine*/ var StateMachine = (function(){ function StateMachine(opts){ this.current = opts.in ...

  8. AngularJS入门基础——$provide.decorator 实例讲解

    <body ng-controller="OneController"> <script>  var Foobar = function() {      ...

  9. alert换行警示

    alert("再次向您问好!在这里,我们向您演示" + '\n' + "如何向警告框添加折行.")

  10. HDU 1229 还是A+B(A+B陶冶情操)

    题目连接:http://acm.hdu.edu.cn/showproblem.php?pid=1229 解题报告:A+B #include<cstdio> int main() { int ...