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 attachEvent传递参数的办法
Dec 14 Javascript
javascript生成随机数的方法
May 16 Javascript
JavaSacript中charCodeAt()方法的使用详解
Jun 05 Javascript
jquery对象和DOM对象的任意相互转换
Feb 21 Javascript
JS中多种方式创建对象详解
Mar 22 Javascript
JavaScript学习笔记之数组求和方法
Mar 23 Javascript
jQuery实现遮罩层登录对话框
Dec 29 Javascript
BootStrap实现鼠标悬停下拉列表功能
Feb 17 Javascript
vue-router 导航钩子的具体使用方法
Aug 31 Javascript
元素全屏的设置与监听实例
Nov 28 Javascript
使用vue实现grid-layout功能实例代码
Jan 05 Javascript
angular 实时监听input框value值的变化触发函数方法
Aug 31 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
Terran热键控制
2020/03/14 星际争霸
从MySQL数据库表中取出随机数据的代码
2007/09/05 PHP
PHP编写学校网站上新生注册登陆程序的实例分享
2016/03/21 PHP
JS保存、读取、换行、转Json报错处理方法
2013/06/14 Javascript
鼠标移到div,浮层显示明细,弹出层与div的上边距左边距重合(示例代码)
2013/12/14 Javascript
jquery实现拖拽调整Div大小
2015/01/30 Javascript
jquery中trigger()无法触发hover事件的解决方法
2015/05/07 Javascript
JS+CSS实现仿msn风格选项卡效果代码
2015/10/22 Javascript
快速学习jQuery插件 jquery.validate.js表单验证插件使用方法
2015/12/01 Javascript
Javascript从数组中随机取出不同元素的两种方法
2016/09/22 Javascript
webuploader模态框ueditor显示问题解决方法
2016/12/27 Javascript
Vue2.0 事件的广播与接收(观察者模式)
2018/03/14 Javascript
jQuery.extend 与 jQuery.fn.extend的用法及区别实例分析
2018/07/25 jQuery
angular学习之动态创建表单的方法
2018/12/07 Javascript
JavaScript设计模式之责任链模式实例分析
2019/01/16 Javascript
微信小程序实现的图片保存功能示例
2019/04/24 Javascript
JQuery获取可视区尺寸和文档尺寸及制作悬浮菜单示例
2019/05/14 jQuery
vue element 关闭当前tab 跳转到上一路由操作
2020/07/22 Javascript
Python 基础教程之闭包的使用方法
2017/09/29 Python
Python设计模式之门面模式简单示例
2018/01/09 Python
tensorflow中next_batch的具体使用
2018/02/02 Python
python opencv 实现读取、显示、写入图像的方法
2020/06/08 Python
通俗易懂了解Python装饰器原理
2020/09/17 Python
selenium3.0+python之环境搭建的方法步骤
2021/02/01 Python
Answear匈牙利:来自全球200多个知名时尚品牌
2017/04/21 全球购物
学年自我鉴定范文
2013/10/01 职场文书
教师评优事迹材料
2014/01/10 职场文书
土木工程专业推荐信
2014/02/19 职场文书
领导干部群众路线教育实践活动个人对照检查材料
2014/09/23 职场文书
个人债务授权委托书范本
2014/10/05 职场文书
小学教师年度个人总结
2015/02/05 职场文书
2015年乡镇人大工作总结
2015/04/22 职场文书
2015年小学英语教师工作总结
2015/05/12 职场文书
2015小学师德工作总结
2015/07/21 职场文书
Python线程池与GIL全局锁实现抽奖小案例
2022/04/13 Python
《勇者辞职不干了》ED主题曲无字幕动画MV公开
2022/04/13 日漫