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 相关文章推荐
for 循环性能比较 提高for循环的效率
Mar 19 Javascript
JavaScript获取和设置CheckBox状态的简单方法
Jul 05 Javascript
浏览器图片选择预览、旋转、批量上传的JS代码实现
Dec 04 Javascript
jQuery Validate 验证,校验规则写在控件中的具体实例
Feb 27 Javascript
js与css实现弹出层覆盖整个页面的方法
Dec 13 Javascript
JavaScript通过使用onerror设置默认图像显示代替alt
Mar 01 Javascript
JS/jquery实现一个网页内同时调用多个倒计时的方法
Apr 27 jQuery
详解vue中computed 和 watch的异同
Jun 30 Javascript
Vue2.0如何发布项目实战
Jul 27 Javascript
js中时间格式化的几种方法
Jul 22 Javascript
vue+mousemove实现鼠标拖动功能(拖动过快失效问题解决方法)
Aug 24 Javascript
jQuery实现简单弹幕制作
Dec 10 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
vBulletin Forum 2.3.xx SQL Injection
2006/10/09 PHP
PHP和Mysqlweb应用开发核心技术 第1部分 Php基础-3 代码组织和重用2
2011/07/03 PHP
分享一个Laravel好用的Cache宏
2015/03/02 PHP
php通过正则表达式记取数据来读取xml的方法
2015/03/09 PHP
PHP实现基于文本的摩斯电码生成器
2016/01/11 PHP
浅谈PHP封装CURL
2019/03/06 PHP
JavaScript高级程序设计
2006/12/29 Javascript
javascript实现上传图片前的预览(TX的面试题)
2007/08/20 Javascript
javascript 面向对象编程 聊聊对象的事
2009/09/17 Javascript
自定义一个jquery插件[鼠标悬浮时候 出现说明label]
2011/06/27 Javascript
javascript操作table(insertRow,deleteRow,insertCell,deleteCell方法详解)
2013/12/16 Javascript
用Jquery.load载入页面实现局部刷新
2014/01/22 Javascript
jQuery操作表格(table)的常用方法、技巧汇总
2014/04/12 Javascript
JS实现动态给图片添加边框的方法
2015/04/01 Javascript
javascript手风琴下拉菜单实现代码
2015/11/12 Javascript
jQuery Html控件基本操作(日常收集整理)
2016/03/11 Javascript
jQuery中layer分页器的使用
2017/03/13 Javascript
JavaScript跳出循环的三种方法(break, return, continue)
2019/07/30 Javascript
在Vue里如何把网页的数据导出到Excel的方法
2020/09/30 Javascript
[03:46]DAC趣味视频-中文考试.mp4
2017/04/02 DOTA
[02:31]2018年度DOTA2最具人气选手-完美盛典
2018/12/16 DOTA
Python实现的多线程同步与互斥锁功能示例
2017/11/30 Python
Python3.5 Pandas模块缺失值处理和层次索引实例详解
2019/04/23 Python
python字典的遍历3种方法详解
2019/08/10 Python
Python concurrent.futures模块使用实例
2019/12/24 Python
解决Tensorflow 使用时cpu编译不支持警告的问题
2020/02/03 Python
OpenCV 表盘指针自动读数的示例代码
2020/04/10 Python
Python numpy大矩阵运算内存不足如何解决
2020/11/19 Python
美国网上花店:JustFlowers
2017/02/12 全球购物
海量信息软件测试笔试题
2015/08/08 面试题
幼儿园英语教学反思
2014/01/30 职场文书
主持词开场白
2014/03/17 职场文书
租房合同协议书
2014/04/09 职场文书
民主生活会对照检查材料
2014/09/22 职场文书
Python利用capstone实现反汇编
2022/04/06 Python
Python线程池与GIL全局锁实现抽奖小案例
2022/04/13 Python