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 数组操作代码集锦
Apr 28 Javascript
Javascript 读书笔记索引贴
Jan 11 Javascript
JS中的异常处理方法分享
Dec 22 Javascript
禁用JavaScript控制台调试的方法
Mar 07 Javascript
jQuery学习笔记之jQuery原型属性和方法
Jun 09 Javascript
JS获取各种宽度、高度的简单介绍
Dec 19 Javascript
javascript使用正则表达式实现去掉空格之后的字符
Feb 15 Javascript
jquery插件star-rating.js实现星级评分特效
Apr 15 Javascript
详解vscode中vue代码颜色插件
Oct 11 Javascript
你了解vue3.0响应式数据怎么实现吗
Jun 07 Javascript
深入浅出vue图片路径的实现
Sep 04 Javascript
Vue+Node服务器查询Mongo数据库及页面数据传递操作实例分析
Dec 20 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中10个不同等级压缩优化图片操作示例
2016/11/14 PHP
Laravel 数据库加密及数据库表前缀配置方法
2019/10/10 PHP
JavaScript入门教程(2) JS基础知识
2009/01/31 Javascript
js实现兼容IE6与IE7的DIV高度
2010/05/13 Javascript
深入理解Javascript中的循环优化
2013/11/09 Javascript
Javascript高级技巧分享
2014/02/25 Javascript
jQuery如何防止这种冒泡事件发生
2015/02/27 Javascript
JavaScript中数据结构与算法(五):经典KMP算法
2015/06/19 Javascript
jQuery实现购物车表单自动结算效果实例
2015/08/10 Javascript
JS实现从顶部下拉显示的带动画QQ客服特效代码
2015/10/24 Javascript
javascript轻量级库createjs使用Easel实现拖拽效果
2016/02/19 Javascript
如何用vue-cli3脚手架搭建一个基于ts的基础脚手架的方法
2019/12/12 Javascript
vue下载二进制流图片操作
2020/10/26 Javascript
[50:45]2018DOTA2亚洲邀请赛 4.6 淘汰赛 VP vs TNC 第一场
2018/04/10 DOTA
[53:52]OG vs EG 2018国际邀请赛淘汰赛BO3 第二场 8.23
2018/08/24 DOTA
详解Python的Django框架中inclusion_tag的使用
2015/07/21 Python
Python 中开发pattern的string模板(template) 实例详解
2017/04/01 Python
ubuntu安装mysql pycharm sublime
2018/02/20 Python
Django利用cookie保存用户登录信息的简单实现方法
2019/05/27 Python
Python Django2.0集成Celery4.1教程
2019/11/19 Python
opencv3/C++实现视频背景去除建模(BSM)
2019/12/11 Python
基于python+selenium的二次封装的实现
2020/01/06 Python
Python实现JS解密并爬取某音漫客网站
2020/10/23 Python
css3 响应式媒体查询的示例代码
2019/09/25 HTML / CSS
德购商城:德国进口直邮商城
2017/06/13 全球购物
ROSEFIELD手表荷兰官方网上商店:北欧极简设计女士腕表品牌
2018/01/24 全球购物
土木工程专业自荐信
2013/10/04 职场文书
致标枪运动员广播稿
2014/02/06 职场文书
全国爱牙日活动总结
2015/02/05 职场文书
社区服务活动感想
2015/08/11 职场文书
六年级作文之关于梦
2019/10/22 职场文书
小程序实现文字循环滚动动画
2021/06/14 Javascript
MySQL中IF()、IFNULL()、NULLIF()、ISNULL()函数的使用详解
2021/06/26 MySQL
MySQL的Query Cache图文详解
2021/07/01 MySQL
JVM的类加载器和双亲委派模式你了解吗
2022/03/13 Java/Android
HTML5基础学习之文本标签控制
2022/03/25 HTML / CSS