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 相关文章推荐
jQuery 源码分析笔记(4) Ready函数
Jun 02 Javascript
JavaScript var声明变量背后的原理示例解析
Oct 12 Javascript
判断javascript的数据类型(示例代码)
Dec 11 Javascript
js类型转换与引用类型详解(Boolean_Number_String)
Mar 07 Javascript
JavaScript中this的9种应用场景及三种复合应用场景
Sep 12 Javascript
JavaScript提高性能知识点汇总
Jan 15 Javascript
js 显示日期时间的实例(时间过一秒加1)
Oct 25 Javascript
Node.js 使用AngularJS的方法示例
May 11 Javascript
浅析前端路由简介以及vue-router实现原理
Jun 01 Javascript
一秒学会微信小程序制作table表格
Feb 14 Javascript
js在HTML的三种引用方式详解
Aug 29 Javascript
Vue使用Element实现增删改查+打包的步骤
Nov 25 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
PHP通过正则表达式下载图片到本地的实现代码
2011/09/19 PHP
解析php 版获取重定向后的地址(代码)
2013/06/26 PHP
php strnatcmp()函数的用法总结
2013/11/27 PHP
linux下实现定时执行php脚本
2015/02/13 PHP
PHP 实现代码复用的一个方法 traits新特性
2015/02/22 PHP
php设计模式之单例模式代码
2016/06/11 PHP
PHP实现接收二进制流转换成图片的方法
2017/01/10 PHP
thinkPHP实现的省市区三级联动功能示例
2017/05/05 PHP
js multiple全选与取消全选实现代码
2012/12/04 Javascript
JavaScript实现快速排序(自已编写)
2012/12/19 Javascript
js类式继承的具体实现方法
2013/12/31 Javascript
jquery实现网页查找功能示例分享
2014/02/12 Javascript
自定义函数实现IE7与IE8不兼容js中trim函数的问题
2015/02/03 Javascript
flash+jQuery实现可关闭及重复播放的压顶广告
2015/04/15 Javascript
JS使用正则表达式除去字符串中重复字符的方法
2015/11/05 Javascript
基于jQuery实现的双11天猫拆红包抽奖效果
2015/12/01 Javascript
微信小程序 石头剪刀布实例代码
2017/01/04 Javascript
ui-router中使用ocLazyLoad和resolve的具体方法
2017/10/18 Javascript
微信小程序实现页面下拉刷新和上拉加载功能详解
2018/12/03 Javascript
解决vue打包后vendor.js文件过大问题
2019/07/03 Javascript
Vue在chrome44偶现点击子元素事件无法冒泡的解决方法
2019/12/15 Javascript
基于JavaScript实现贪吃蛇游戏
2020/03/16 Javascript
[01:04:02]DOTA2-DPC中国联赛 正赛 Elephant vs IG BO3 第二场 1月24日
2021/03/11 DOTA
在Linux上安装Python的Flask框架和创建第一个app实例的教程
2015/03/30 Python
Python设计模式之备忘录模式原理与用法详解
2019/01/15 Python
python 批量解压压缩文件的实例代码
2019/06/27 Python
对YOLOv3模型调用时候的python接口详解
2019/08/26 Python
BeautifulSoup获取指定class样式的div的实现
2020/12/07 Python
关于python中remove的一些坑小结
2021/01/04 Python
孤独星球出版物:Lonely Planet Publications
2018/03/17 全球购物
Harrods英国:世界领先的奢侈品百货商店
2020/09/23 全球购物
元宵晚会主持词
2014/03/25 职场文书
奠基仪式策划方案
2014/05/15 职场文书
校园演讲稿汇总
2014/05/21 职场文书
西双版纳导游词
2015/02/03 职场文书
争做文明公民倡议书
2019/06/24 职场文书