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 相关文章推荐
js 实现打印网页中定义的部分内容的代码
Apr 01 Javascript
html中table数据排序的js代码
Aug 09 Javascript
30分钟就入门的正则表达式基础教程
Feb 25 Javascript
jquery中子元素和后代元素的区别示例介绍
Apr 02 Javascript
javascript 操作符(~、&amp;、|、^、)使用案例
Dec 31 Javascript
jQuery中[attribute!=value]选择器用法实例
Dec 31 Javascript
PhotoShop给图片自动添加边框及EXIF信息的JS脚本
Feb 15 Javascript
js实现页面跳转的几种方法小结
May 16 Javascript
vue-scroller记录滚动位置的示例代码
Jan 17 Javascript
使用Vue实现图片上传的三种方式
Jul 17 Javascript
解决vue单页面 回退页面 keeplive 缓存问题
Jul 22 Javascript
如何用JS实现网页瀑布流布局
Apr 24 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
PHP 导出数据到淘宝助手CSV的方法分享
2010/02/27 PHP
ThinkPHP中U方法的使用浅析
2014/06/13 PHP
PHP中的Streams详细介绍
2014/11/12 PHP
超详细的php用户注册页面填写信息完整实例(附源码)
2015/11/17 PHP
前端必学之PHP语法基础
2016/01/01 PHP
laravel5.2实现区分前后台用户登录的方法
2017/01/11 PHP
Laravel框架表单验证操作实例分析
2019/09/30 PHP
jquery的Tooltip插件 qtip使用详细说明
2010/09/08 Javascript
extjs实现选择多表自定义查询功能 前台部分(ext源码)
2011/12/20 Javascript
使用Jquery Aajx访问WCF服务(GET、POST、PUT、DELETE)
2012/03/16 Javascript
js中继承的几种用法总结(apply,call,prototype)
2013/12/26 Javascript
Javascript中使用A标签获取当前目录的绝对路径方法
2015/03/02 Javascript
jquery 将当前时间转换成yyyymmdd格式的实现方法
2016/06/01 Javascript
使用JQuery中的trim()方法去掉前后空格
2016/09/16 Javascript
BootStrop前端框架入门教程详解
2016/12/25 Javascript
了解VUE的render函数的使用
2017/06/08 Javascript
js使用原型对象(prototype)需要注意的地方
2017/08/28 Javascript
使用html+js+css 实现页面轮播图效果(实例讲解)
2017/09/21 Javascript
JavaScript模拟实现封装的三种方式及写法区别
2017/10/27 Javascript
JS+HTML5 Canvas实现简单的写字板功能示例
2018/08/30 Javascript
[jQuery] 事件和动画详解
2019/03/05 jQuery
JS图片懒加载的优点及实现原理
2020/01/10 Javascript
Node.js API详解之 assert模块用法实例分析
2020/05/26 Javascript
[01:03:38]2014 DOTA2国际邀请赛中国区预选赛5.21 CNB VS CIS
2014/05/22 DOTA
使用Python调取任意数字资产钱包余额功能
2019/08/15 Python
python 用户交互输入input的4种用法详解
2019/09/24 Python
Python面向对象程序设计之私有变量,私有方法原理与用法分析
2020/03/23 Python
美国眼镜在线零售商:Dualens
2019/12/07 全球购物
给医务人员表扬信
2014/01/12 职场文书
家教广告词
2014/03/19 职场文书
老公保证书
2015/01/17 职场文书
学校青年志愿者活动总结
2015/05/06 职场文书
Python文件的操作示例的详细讲解
2021/04/08 Python
golang 定时任务方面time.Sleep和time.Tick的优劣对比分析
2021/05/05 Golang
Mysql中有关Datetime和Timestamp的使用总结
2021/12/06 MySQL
GTX1660显卡搭配显示器推荐
2022/04/19 数码科技