JavaScript EventEmitter 背后的秘密 完整版


Posted in Javascript onMarch 29, 2018

什么是 Event Emitter?

Event emitter 听起来只是触发一个事件,这个事件任何东西都能监听。

想象一下这样的场景,在你的异步代码中,去“呼叫”一些事件的发生,以及让你其他部分都要听到你的“呼叫”并且注册他们的想法。

为了不同的目的,对于 Event Emitter 模式有大量不同的实现,但是基本的想法是为了给一个框架提供事件的管理以及能够去订阅他们。

在这里,我们的目标创建属于我们自己的 Event Emitter 去理解背后的秘密。所以,让我们看一下下面的代码是怎么工作的。

let input = document.querySelector("input[type="text"]");
let button = document.querySelector("button");
let h1 = document.querySelector("h1");

button.addEventListener("click", () => {
  emitter.emit("event:name-changed", { name: input.value });
});

let emitter = new EventEmitter();
emitter.subscribe("event:name-changed", data => {
  h1.innerHTML = `Your name is: ${data.name}`;
});

让我们开始。

class EventEmitter {
  constructor() {
    this.events = {};
  }
}

我们先创建一个 EventEmiiter 类以及初始化 events 空对象属性。这个 events 属性的目的是为了存储我们的事件集合,这个 events 对象使用事件名当做 key,用订阅者集合当做 value。(可以把每个订阅者看作是一个函数)。

订阅函数

subscribe(eventName, fn) {
  if (!this.events[eventName]) {
    this.events[eventName] = [];
  }

  this.events[eventName].push(fn);
}

这个订阅函数获取事件名称,在我们之前的例子中,它是 "event:name-changed" 以及传入一个回调,当有人调用 emit(或尖叫)事件的时候调用回调。

在 JavaScript 函数的优点之一是函数是第一对象,所以我们能像之前我们的订阅方法一样,通过函数作为另一个函数的参数。

如果未注册这个事件,我们需要在第一次为它设置一个初始值,事件名称作为 key 以及初始化一个空数组赋值给它,然后我们将函数放入这个数组,以便我们想通过 emit 去调用这个事件。

调用函数

emit(eventName, data) {
  const event = this.events[eventName];
  if (event) {
    event.forEach(fn => {
      fn.call(null, data);
    });
  }
}

这个调用函数接受事件名,这个事件名是我们想“呼叫”的名称,以及我们想传递给这个事件的数据。如果在我们的 events 中存在这个事件,我们将带上数据循环调用所有订阅的方法。

使用上面的代码能做我们所说的全部的事情。但我们仍然有一个问题。当我们不再需要它们的时候,我们需要一种方法来取消注册这些订阅,因为如果你不这样做,将造成内存泄漏。

让我们来解决这个问题,通过在订阅函数中返回一个取消注册的方法。

subscribe(eventName, fn) {
  if (!this.events[eventName]) {
    this.events[eventName] = [];
  }

  this.events[eventName].push(fn);

  return () => {
    this.events[eventName] = this.events[eventName].filter(eventFn => fn !== eventFn);
  }
}

因为 JavaScript 函数是第一对象,你能在一个函数中返回一个函数。因此现在我们能调用这个取消注册函数,如下:

let unsubscribe = emitter.subscribe("event:name-changed", data => console.log(data));

unsubscribe();

当我们调用取消注册函数的时候,我们删除的功能依赖于对订阅函数集合的筛选方法(Array filter)。

和内存泄露说再见!??

你能运行这份代码,所有代码都在这里。

html代码

<!DOCTYPE html>
<html>
<head>
	<script src="script.js"></script>
</head>
<body>
	<input type="text">
	<h1></h1>
	<button>Change name</button>
</body>
</html>

js代码

class EventEmitter {
 constructor() {
  this.events = {};
 }

 emit(eventName, data) {
  const event = this.events[eventName];
  if (event) {
   event.forEach(fn => {
    fn.call(null, data);
   });
  }
 }

 subscribe(eventName, fn) {
  if (!this.events[eventName]) {
   this.events[eventName] = [];
  }

  this.events[eventName].push(fn);
  return () => {
   this.events[eventName] = this.events[eventName].filter(eventFn => fn !== eventFn);
  }
 }


}

document.addEventListener("DOMContentLoaded", function (event) {
 let input = document.querySelector('input[type="text"]');
 let button = document.querySelector('button');
 let h1 = document.querySelector('h1');

 button.addEventListener('click', () => {
  emitter.emit('event:name-changed', { name: input.value });
 });

 let emitter = new EventEmitter();
 emitter.subscribe('event:name-changed', data => {
  h1.innerHTML = `Your name is: ${data.name}`;
 });
});

注:这份代码可能需要翻墙或者特别慢,所以我放到了 三水点靠木 上,大家可以下载EventEmitter-3water.rar。

原文出自:https://medium.com/@NetanelBasal/javascript-the-magic-behind-event-emitter-cce3abcbcef9#.nzgbagnxe

Javascript 相关文章推荐
JS中toFixed()方法引起的问题如何解决
Nov 20 Javascript
js实现拖拽 闭包函数详细介绍
Nov 25 Javascript
Jquery Validate 正则表达式实用验证代码大全
Aug 23 Javascript
使用mouse事件实现简单的鼠标经过特效
Jan 30 Javascript
JavaScript中逗号运算符介绍及使用示例
Mar 13 Javascript
第九章之路径分页标签与徽章组件
Apr 25 Javascript
jQuery自制提示框tooltip改进版
Aug 01 Javascript
jquery动态添加文本并获取值的方法
Oct 12 Javascript
微信小程序 地图map详解及简单实例
Jan 10 Javascript
深入理解ES6中let和闭包
Feb 22 Javascript
微信小程序使用npm支持踩坑
Nov 07 Javascript
vue使用wavesurfer.js解决音频可视化播放问题
Apr 04 Vue.js
vue的diff算法知识点总结
Mar 29 #Javascript
vue文件树组件使用详解
Mar 29 #Javascript
vue全局组件与局部组件使用方法详解
Mar 29 #Javascript
javascript实现文件拖拽事件
Mar 29 #Javascript
vue 父组件调用子组件方法及事件
Mar 29 #Javascript
vue.js element-ui tree树形控件改iview的方法
Mar 29 #Javascript
Vue 源码分析之 Observer实现过程
Mar 29 #Javascript
You might like
冰滴咖啡制作步骤
2021/03/03 冲泡冲煮
PHP JSON格式数据交互实例代码详解
2011/01/13 PHP
PHP中usort在值相同时改变原始位置问题的解决方法
2011/11/27 PHP
PHP生成静态HTML文档实现代码
2016/06/23 PHP
JQUERY1.6 使用方法四 检测浏览器
2011/11/23 Javascript
javascript的BOM汇总
2015/07/16 Javascript
js实现适用于素材网站的黑色多级菜单导航条效果
2015/08/24 Javascript
node.js连接mongoDB数据库 快速搭建自己的web服务
2016/04/17 Javascript
JS定时器使用,定时定点,固定时刻,循环执行详解
2016/05/31 Javascript
JavaScript中关于iframe滚动条的去除和保留
2016/11/17 Javascript
Javascript 两种刷新方法以及区别和适用范围
2017/01/17 Javascript
Angular2使用jQuery的方法教程
2017/05/28 jQuery
vue.js内置组件之keep-alive组件使用
2018/07/10 Javascript
详解React之key的使用和实践
2018/09/29 Javascript
jQuery 获取除某指定对象外的其他对象 ( :not() 与.not())
2018/10/10 jQuery
详解JavaScript原生封装ajax请求和Jquery中的ajax请求
2019/02/14 jQuery
vue 在服务器端直接修改请求的接口地址
2020/12/19 Vue.js
Python中的魔法方法深入理解
2014/07/09 Python
Python脚本实现代码行数统计代码分享
2015/03/10 Python
Python使用smtp和pop简单收发邮件完整实例
2018/01/09 Python
python 机器学习之支持向量机非线性回归SVR模型
2019/06/26 Python
python创建n行m列数组示例
2019/12/02 Python
python paramiko远程服务器终端操作过程解析
2019/12/14 Python
python上selenium的弹框操作实现
2020/07/13 Python
Python+Kepler.gl实现时间轮播地图过程解析
2020/07/20 Python
移动端html5判断是否滚动到底部并且下拉加载
2019/11/19 HTML / CSS
赡养老人协议书
2014/04/21 职场文书
八一建军节演讲稿
2014/09/10 职场文书
酒店餐厅2014重阳节活动策划方案
2014/09/16 职场文书
贵阳市党的群众路线教育实践活动党(工)委领导班子整改方案
2014/10/26 职场文书
平安建设汇报材料
2014/12/29 职场文书
以权谋私检举信范文
2015/03/02 职场文书
2015年度优秀员工推荐信
2015/03/23 职场文书
如何撰写出一份完美的商业计划书?
2019/07/12 职场文书
导游词之张家界
2019/10/31 职场文书
Python利用capstone实现反汇编
2022/04/06 Python