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 写类方式之九
Jul 05 Javascript
Jquery Ajax学习实例7 Ajax所有过程事件分析示例
Mar 23 Javascript
基于jQuery的实现简单的分页控件
Oct 10 Javascript
JS判断字符串长度的5个方法(区分中文和英文)
Mar 18 Javascript
使用jquery.validate自定义方法实现&quot;手机号码或者固话至少填写一个&quot;的逻辑验证
Sep 01 Javascript
node.js中的fs.readFileSync方法使用说明
Dec 15 Javascript
JS实现六位字符密码输入器功能
Aug 19 Javascript
bootstrap daterangepicker双日历时间段选择控件详解
Jun 15 Javascript
JS获取填报扩展单元格控件的值的解决办法
Jul 14 Javascript
Web开发使用Angular实现用户密码强度判别的方法
Sep 27 Javascript
关于vue编译版本引入的问题的解决
Sep 17 Javascript
不刷新网页就能链接新的js文件方法总结
Mar 01 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实现把数组按指定的个数分隔
2014/02/17 PHP
Codeigniter生成Excel文档的简单方法
2014/06/12 PHP
PHP查找数值数组中不重复最大和最小的10个数的方法
2015/04/20 PHP
php 的反射详解及示例代码
2016/08/25 PHP
PHP编程中的Session阻塞问题与解决方法分析
2017/08/07 PHP
PHP集成环境XAMPP的安装与配置
2018/11/13 PHP
PHP生成指定范围内的N个不重复的随机数
2019/03/18 PHP
JQuery动态给table添加、删除行 改进版
2011/01/19 Javascript
通过jQuery源码学习javascript(二)
2012/12/27 Javascript
jQuery 取值、赋值的基本方法整理
2014/03/31 Javascript
jquery 判断滚动条到达了底部和顶端的方法
2014/04/02 Javascript
jQuery回调函数的定义及用法实例
2014/12/23 Javascript
Jquery树插件zTree用法入门教程
2015/02/17 Javascript
JavaScript中的立即执行函数表达式介绍
2015/03/15 Javascript
JS实用的动画弹出层效果实例
2015/05/05 Javascript
浅谈JavaScript中的Math.atan()方法的使用
2015/06/14 Javascript
jQuery Easyui 下拉树组件combotree
2016/12/16 Javascript
jQuery实现限制文本框的输入长度
2017/01/11 Javascript
Node.js之网络通讯模块实现浅析
2017/04/01 Javascript
JS中promise化微信小程序api
2018/04/12 Javascript
Nuxt.js实现校验访问浏览器类型的中间件
2018/08/24 Javascript
浅析JavaScript 函数防抖和节流
2020/07/13 Javascript
vue-router为激活的路由设置样式操作
2020/07/18 Javascript
浅析Python中的序列化存储的方法
2015/04/28 Python
python 中字典嵌套列表的方法
2018/07/03 Python
Python Dict找出value大于某值或key大于某值的所有项方式
2020/06/05 Python
复古风格的女装和装饰品:ModCloth
2017/12/29 全球购物
iHerb中文官网:维生素、保健品和健康产品
2018/11/01 全球购物
PatPat阿根廷:妈妈们的购物平台
2019/05/30 全球购物
进修护士自我鉴定
2013/10/14 职场文书
个人简历自我评价八例
2013/10/31 职场文书
《水上飞机》教学反思
2014/04/10 职场文书
煤矿百日安全活动总结
2015/05/07 职场文书
民事诉讼代理词
2015/05/25 职场文书
多属性、多分类MySQL模式设计
2021/04/05 MySQL
Android开发实现极为简单的QQ登录页面
2022/04/24 Java/Android