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

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

  发布-订阅模式是前端常用的一种设计模式,现在主流的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. ASP.Net执行cmd命令的实现代码

    using System; using System.Collections; using System.Configuration; using System.Data; using System. ...

  2. ubuntu14的unity desktop显示异常

    在多用户下,卸载compiz后某个单一用户出现菜单栏和任务栏图标消失的情况. 这时,需要在图形界面下重置compiz $dconf reset -f /org/compiz/ 重启unity $set ...

  3. java基础基础总结----- Math(随机数)

    前言:math类中感觉最好玩的应该就是随机数 代码: package com.day13.math; import java.util.Random; /** * 类说明 :Math * @autho ...

  4. es7----proxy

    proxy是代理的意思,es7新增这个可以代理某个变量的“增删改查”,vue的核心原理就是这个~~ 基本使用方法: let json = {a: 123, c: 999} let p = new Pr ...

  5. python---django中模板渲染(csrf令牌使用,自定义模板函数)

    使用终端,可以更方便的去实验,但是没有提示信息: 在项目目录下: D:\MyPython\day23\HelloWorld>python manage.py shell 开始实验: >&g ...

  6. ifconfig不显示网卡eth0

    参考资料:http://blog.itpub.net/25851087/viewspace-1700568/ 在/etc/sysconfig/network-script/ifcfg-eth0网卡配置 ...

  7. 转自知乎大神---什么是 JS 原型链?

    我们知道 JS 有对象,比如 var obj = { name: 'obj' } 我们可以对 obj 进行一些操作,包括 「读」属性 「新增」属性 「更新」属性 「删除」属性 下面我们主要来看一下「读 ...

  8. WordPress中使用Markdown和Syntax Highlighter

    下载安装插件 在wordpress插件中安装WP Code Prettify. PHP Markdown Extra 下载Extra,并上传安装到wordpress. Code Prettify th ...

  9. CSS absolute与relative不得不说的故事

    写在开篇: absolute说:“relative,我这辈子都不想看见你!” 为什么呢?它们明明那么相亲相爱,形影不离,这之中到底发生了什么不为人知的故事,竟然让absolute如此讨厌relativ ...

  10. 使用 scm-manager 搭建 git/svn 代码管理仓库(一)

    1.在官网上下载scm-manager 下载地址  https://www.scm-manager.org/download/ 选择下载文件 2. 配置java 环境 参照文章:https://jin ...