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(1.3.2) 7行代码搞定跟随屏幕滚动的层
May 21 Javascript
JS无限树状列表实现代码
Jan 11 Javascript
JavaScript中的console.group()函数详细介绍
Dec 29 Javascript
javascript实现实时输出当前的时间
Apr 27 Javascript
Bootstrap组件学习之导航、标签、面包屑导航(精品)
May 17 Javascript
JS组件Bootstrap Select2使用方法解析
May 30 Javascript
BootStrap入门教程(二)之固定的内置样式
Sep 19 Javascript
微信小程序 配置文件详细介绍
Dec 14 Javascript
php register_shutdown_function函数详解
Jul 23 Javascript
分析javascript原型及原型链
Mar 18 Javascript
javascript canvas检测小球碰撞
Apr 17 Javascript
vue中h5端打开app(判断是安卓还是苹果)
Feb 26 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将数据库中所有内容生成静态html文档的代码
2010/04/12 PHP
浅析PHP的ASCII码转换类
2013/07/05 PHP
PHP的Yii框架中移除组件所绑定的行为的方法
2016/03/18 PHP
php基于curl实现的股票信息查询类实例
2016/11/11 PHP
php+ajax实现无刷新文件上传功能(ajaxuploadfile)
2018/02/11 PHP
浅谈PHP中pack、unpack的详细用法
2018/03/12 PHP
javascript 跳转代码集合
2009/12/03 Javascript
SWFObject 2.1以上版本语法介绍
2010/07/10 Javascript
jQuery中操控hidden、disable等无值属性的方法
2014/01/06 Javascript
wap手机图片滑动切换特效无css3元素js脚本编写
2014/07/28 Javascript
JavaScript中使用Object.prototype.toString判断是否为数组
2015/04/01 Javascript
javascript实现可全选、反选及删除表格的方法
2015/05/15 Javascript
jQuery实现键盘回车搜索功能
2017/07/25 jQuery
如何解决js函数防抖、节流出现的问题
2019/06/17 Javascript
小程序和web画三角形实现解析
2019/09/02 Javascript
Python中使用urllib2防止302跳转的代码例子
2014/07/07 Python
Python psutil模块简单使用实例
2015/04/28 Python
Python构建XML树结构的方法示例
2017/06/30 Python
python实现上传下载文件功能
2020/11/19 Python
Python中的集合介绍
2019/01/28 Python
Django框架设置cookies与获取cookies操作详解
2019/05/27 Python
实例详解python函数的对象、函数嵌套、名称空间和作用域
2019/05/31 Python
Python3离线安装Requests模块问题
2019/10/13 Python
TensorBoard 计算图的查看方式
2020/02/15 Python
浅谈Python程序的错误:变量未定义
2020/06/02 Python
Python 中的函数装饰器和闭包详解
2021/02/06 Python
HTML5+CSS3绘制锯齿状的矩形
2016/03/01 HTML / CSS
俄罗斯购买自行车网站:Vamvelosiped
2021/01/29 全球购物
简历自荐信
2013/12/02 职场文书
财务负责人任命书
2014/06/06 职场文书
低碳环保口号
2014/06/12 职场文书
防灾减灾标语
2014/10/07 职场文书
党员群众路线教育实践活动学习笔记
2014/11/05 职场文书
《蜜蜂引路》教学反思
2016/02/22 职场文书
导游词之湖北梁子湖
2019/11/07 职场文书
详解PHP Swoole与TCP三次握手
2021/05/27 PHP