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 相关文章推荐
json数据与字符串的相互转化示例
Sep 18 Javascript
JavaScript检测字符串中是否含有html标签实现方法
Jul 01 Javascript
jquery实现简单文字提示效果
Dec 02 Javascript
JS获取中文拼音首字母并通过拼音首字母快速查找页面内对应中文内容的方法【附demo源码】
Aug 19 Javascript
解析AngularJS中get请求URL出现的跨域问题
Dec 01 Javascript
Vue2单一事件管理组件通信
May 09 Javascript
详解vue2父组件传递props异步数据到子组件的问题
Jun 29 Javascript
解决VUE框架 导致绑定事件的阻止冒泡失效问题
Feb 24 Javascript
Vue 实现前进刷新后退不刷新的效果
Jun 14 Javascript
react中Suspense的使用详解
Sep 01 Javascript
Vue组件为什么data必须是一个函数
Jun 11 Javascript
javascript操作向表格中动态加载数据
Aug 27 Javascript
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
NOT NULL 和NULL
2007/01/15 PHP
PHP的array_diff()函数在处理大数组时的效率问题
2011/11/27 PHP
VIM中设置php自动缩进为4个空格的方法详解
2013/06/14 PHP
解密ThinkPHP3.1.2版本之独立分组功能应用
2014/06/19 PHP
php获取指定日期之间的各个周和月的起止时间
2014/11/24 PHP
php实现的rc4加密解密类定义与用法示例
2018/08/16 PHP
Laravel 连接(Join)示例
2019/10/16 PHP
jQuery 使用手册(七)
2009/09/23 Javascript
JavaScript访问样式表代码
2010/10/15 Javascript
jquery 插件学习(四)
2012/08/06 Javascript
Android中资源文件(非代码部分)的使用概览
2012/12/18 Javascript
js中document.write使用过程中的一点疑问解答
2014/03/20 Javascript
javascript中Array数组的迭代方法实例分析
2015/02/04 Javascript
JavaScript实现图片DIV竖向滑动的方法
2015/04/25 Javascript
在JavaScript中操作时间之getYear()方法的使用教程
2015/06/11 Javascript
JavaScript实现添加、查找、删除元素
2015/07/02 Javascript
浅析Javascript中bind()方法的使用与实现
2016/05/30 Javascript
js倒计时显示实例
2016/12/11 Javascript
超全面的javascript中变量命名规则
2017/02/09 Javascript
angularjs实现搜索的关键字在正文中高亮出来
2017/06/13 Javascript
Angular使用 ng-img-max 调整浏览器中的图片的示例代码
2017/08/17 Javascript
JavaScript 链表定义与使用方法示例
2020/04/28 Javascript
Vue触发input选取文件点击事件操作
2020/08/07 Javascript
Python实现针对中文排序的方法
2017/05/09 Python
Python中常用信号signal类型实例
2018/01/25 Python
Python及Django框架生成二维码的方法分析
2018/01/31 Python
python爬取盘搜的有效链接实现代码
2019/07/20 Python
python实现按关键字筛选日志文件
2019/12/24 Python
Django调用百度AI接口实现人脸注册登录代码实例
2020/04/23 Python
25道Java面试题集合
2013/05/21 面试题
vue 中 get / delete 传递数组参数方法
2021/03/23 Vue.js
致短跑运动员广播稿
2014/01/09 职场文书
领导干部培训感言
2014/01/23 职场文书
学习十八届三中全会精神实施方案
2014/02/17 职场文书
民生工作实施方案
2014/05/31 职场文书
思想品德课教学反思
2016/02/24 职场文书