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 相关文章推荐
在Javascript中为String对象添加trim,ltrim,rtrim方法
Sep 22 Javascript
学习ExtJS border布局
Oct 08 Javascript
jQuery EasyUI 中文API Button使用实例
Apr 14 Javascript
jQuery获取当前对象标签名称的方法
Feb 07 Javascript
jQuery使用hide方法隐藏元素自身用法实例
Mar 30 Javascript
仅一个form表单 js实现注册信息依次填写提交功能
Jun 12 Javascript
从0开始学Vue
Oct 27 Javascript
详解ES6中的let命令
Apr 05 Javascript
Javascript实现找不同色块的游戏
Jul 17 Javascript
详解axios 全攻略之基本介绍与使用(GET 与 POST)
Sep 15 Javascript
js判断传入时间和当前时间大小实例(超简单)
Jan 11 Javascript
基于vue-cli3+typescript的tsx开发模板搭建过程分享
Feb 28 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
PHP数组交集的优化代码分析
2011/03/06 PHP
php检查字符串中是否包含7位GSM字符的方法
2015/03/17 PHP
php实现RSA加密类实例
2015/03/26 PHP
php实现有序数组打印或排序的方法【附Python、C及Go语言实现代码】
2016/11/10 PHP
php实现水印文字和缩略图的方法示例
2016/12/29 PHP
php 静态属性和静态方法区别详解
2017/04/09 PHP
Mootools 1.2教程 类(一)
2009/09/15 Javascript
定义JavaScript二维数组采用定义数组的数组来实现
2012/12/09 Javascript
node.js中的path.join方法使用说明
2014/12/08 Javascript
轻松创建nodejs服务器(8):非阻塞是如何实现的
2014/12/18 NodeJs
JS数组搜索之折半搜索实现方法分析
2017/03/27 Javascript
不使用 JS 匿名函数理由
2017/11/17 Javascript
Puppet的一些技巧
2018/09/17 Javascript
axios 封装上传文件的请求方法
2018/09/26 Javascript
JavaScript强制类型转换和隐式类型转换操作示例
2019/05/01 Javascript
vue router动态路由设置参数可选问题
2019/08/21 Javascript
深入webpack打包原理及loader和plugin的实现
2020/05/06 Javascript
vue-i18n实现中英文切换的方法
2020/07/06 Javascript
[46:32]Fnatic vs OG 2018国际邀请赛小组赛BO2 第一场 8.18
2018/08/19 DOTA
[01:06:54]DOTA2-DPC中国联赛 正赛 SAG vs DLG BO3 第二场 2月28日
2021/03/11 DOTA
在Django的通用视图中处理Context的方法
2015/07/21 Python
使用Python编写简单的画图板程序的示例教程
2015/12/08 Python
django传值给模板, 再用JS接收并进行操作的实例
2018/05/28 Python
解决pycharm每次新建项目都要重新安装一些第三方库的问题
2019/01/17 Python
详解Python的三种可变参数
2019/05/08 Python
python中的数据结构比较
2019/05/13 Python
python实现智能语音天气预报
2019/12/02 Python
Python 爬虫的原理
2020/07/30 Python
Currentbody美国/加拿大:美容仪专家
2020/03/09 全球购物
大学生毕业鉴定
2014/01/31 职场文书
2014年教研员工作总结
2014/12/23 职场文书
2014年生活老师工作总结
2014/12/23 职场文书
Mysql 设置boolean类型的操作
2021/06/04 MySQL
如何用Python搭建gRPC服务
2021/06/30 Python
Golang 语言控制并发 Goroutine的方法
2021/06/30 Golang
利用For循环遍历Python字典的三种方法实例
2022/03/25 Python