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编程起步(第一课)
Jan 10 Javascript
window.print打印指定div实例代码
Dec 13 Javascript
初识SmartJS - AOP三剑客
Jun 08 Javascript
jQuery使用$.ajax进行即时验证实例详解
Dec 11 Javascript
jquery实现输入框实时输入触发事件代码
Dec 21 Javascript
从零学习node.js之express入门(六)
Feb 25 Javascript
详解webpack与SPA实践之开发环境搭建
Dec 18 Javascript
详解基于vue的服务端渲染框架NUXT
Jun 20 Javascript
vue.js内置组件之keep-alive组件使用
Jul 10 Javascript
React项目动态设置title标题的方法示例
Sep 26 Javascript
详解使用React.memo()来优化函数组件的性能
Mar 19 Javascript
详解jquery和vue对比
Apr 16 jQuery
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下MAIL的另一解决方案
2006/10/09 PHP
PHP中函数内引用全局变量的方法
2008/10/20 PHP
ThinkPHP模板范围判断输出In标签与Range标签用法详解
2014/06/30 PHP
php获取开始与结束日期之间所有日期的方法
2016/11/29 PHP
JavaScript进阶教程(第四课第一部分)
2007/04/05 Javascript
Jquery选中或取消radio示例
2013/09/29 Javascript
javascript中数组array及string的方法总结
2014/11/28 Javascript
NodeJS学习笔记之Connect中间件模块(二)
2015/01/27 NodeJs
JavaScript 面向对象与原型
2015/04/10 Javascript
AngularJS constant和value区别详解
2017/02/28 Javascript
bootstrap Table实现合并相同行
2019/07/19 Javascript
Node 使用express-http-proxy 做api网关的实现
2020/10/15 Javascript
vue中解决微信html5原生ios虚拟键返回不刷新问题
2020/10/20 Javascript
使用vue编写h5公众号跳转小程序的实现代码
2020/11/27 Vue.js
vue-cli4.0多环境配置变量与模式详解
2020/12/30 Vue.js
[01:08]DOTA2“血战之命”预告片
2017/08/12 DOTA
Python压缩和解压缩zip文件
2015/02/14 Python
Python的Django中将文件上传至七牛云存储的代码分享
2016/06/03 Python
Python使用django搭建web开发环境
2017/06/09 Python
利用Python如何批量修改数据库执行Sql文件
2018/07/29 Python
Python 使用PIL中的resize进行缩放的实例讲解
2018/08/03 Python
Python构建图像分类识别器的方法
2019/01/12 Python
python中正则表达式与模式匹配
2019/05/07 Python
Django 对象关系映射(ORM)源码详解
2019/08/06 Python
Python中的list与tuple集合区别解析
2019/10/12 Python
Python+Kepler.gl实现时间轮播地图过程解析
2020/07/20 Python
python海龟绘图之画国旗实例代码
2020/11/11 Python
大学学习生活感言
2014/01/18 职场文书
教师师德反思材料
2014/02/15 职场文书
感恩祖国演讲稿
2014/09/09 职场文书
2014年医院科室工作总结
2014/12/20 职场文书
自我推荐信格式模板
2015/03/24 职场文书
2015年毕业生个人自荐书
2015/03/24 职场文书
Java反应式框架Reactor中的Mono和Flux
2021/07/25 Java/Android
MySQL笔记 —SQL运算符
2022/01/18 MySQL
Java 死锁解决方案
2022/05/11 Java/Android