JavaScript EventEmitter 背后的秘密 完整版

什么是 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-jb51.rar。

原文出自:https://medium.com/@NetanelBasal/javascript-the-magic-behind-event-emitter-cce3abcbcef9#.nzgbagnxe

您可能感兴趣的文章:

  • 从零开始学习Node.js系列教程六:EventEmitter发送和接收事件的方法示例
  • 关于Node.js的events.EventEmitter用法介绍
  • node.js中的events.EventEmitter.listenerCount方法使用说明
  • 理解 JavaScript EventEmitter
(0)

相关推荐

  • 从零开始学习Node.js系列教程六:EventEmitter发送和接收事件的方法示例

    本文实例讲述了Node.js EventEmitter发送和接收事件的方法.分享给大家供大家参考,具体如下: pulser.js /* EventEmitter发送和接收事件 HTTPServer和HTTPClient类,它们都继承自EventEmitter EventEmitter被定义在Node的事件(events)模块中,直接使用EventEmitter类需要先声明require('events'), 否则不必显式声明require('events'),因为Node中很多对象都无需你调用r

  • 关于Node.js的events.EventEmitter用法介绍

    Node.js 所有的异步 I/O 操作在完成时都会发送一个事件到事件队列. Node.js里面的许多对象都会分发事件:一个net.Server对象会在每次有新连接时分发一个事件, 一个fs.readStream对象会在文件被打开的时候发出一个事件. 所有这些产生事件的对象都是 events.EventEmitter 的实例. EventEmitter 类 events 模块只提供了一个对象: events.EventEmitter.EventEmitter 的核心就是事件触发与事件监听器功能的

  • node.js中的events.EventEmitter.listenerCount方法使用说明

    方法说明: 返回注册了指定事件的监听器数量. 语法: 复制代码 代码如下: EventEmitter.listenerCount(emitter, event) 接收参数: emitter             事件发射器 event                事件 例子: 复制代码 代码如下: if(events.EventEmitter.listenerCount(this, 'feedback') == 0) {     //.... } 源码: 复制代码 代码如下: EventEm

  • 理解 JavaScript EventEmitter

    2个多月前把 Github 上的 eventemitter3 和 Node.js 下的事件模块 events 的源码抄了一遍,才终于对 JavaScript 事件有所了解. 上个周末花点时间根据之前看源码的理解自己用 ES6 实现了一个 eventemitter8,然后也发布到 npm 上了,让我比较意外的是才发布两天在没有 readme 介绍,没有任何宣传的情况下居然有45个下载,我很好奇都是谁下载的,会不会用.我花了不少时间半抄半原创的一个 JavaScript 时间处理库 now.js (

  • JavaScript EventEmitter 背后的秘密 完整版

    什么是 Event Emitter? Event emitter 听起来只是触发一个事件,这个事件任何东西都能监听. 想象一下这样的场景,在你的异步代码中,去"呼叫"一些事件的发生,以及让你其他部分都要听到你的"呼叫"并且注册他们的想法. 为了不同的目的,对于 Event Emitter 模式有大量不同的实现,但是基本的想法是为了给一个框架提供事件的管理以及能够去订阅他们. 在这里,我们的目标创建属于我们自己的 Event Emitter 去理解背后的秘密.所以,让

  • JavaScript订单操作小程序完整版

    本文实例为大家分享了完整的订单操作小程序(增加订单,删除订单,修改订单数量),供大家参考,具体内容如下 <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Title</title> <style> table { text-align: center; } </style> <

  • javascript贪吃蛇完整版(源码)

    javascript贪吃蛇完整版 注释完整,面向对象 复制代码 代码如下: <html><head>    <title>贪吃蛇 Snake v2.4</title><style>    body{        font-size:9pt;    }    table{        border-collapse: collapse;        border:solid #333 1px;    }    td{        heigh

  • JavaScript、tab切换完整版(自动切换、鼠标移入停止、移开运行)

    效果图如下所示: 废话不多说了,直接给大家贴js代码了. <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>table切换</title> <style type="text/css"> *{ padding: } button{ width: 95px; } .active

  • 动力节点_王勇_DRP项目视频教程完整版292集

    该视频由国内知名讲师王勇老师主讲,适合掌握Java基础内容的同学学习,本视频共计292集,该视频是Java培训领域中技术涉及面最广,讲解最透彻,资料最完整的视频,DRP视频在Java培训领域中产生了非常大的影响,学习Java Web项目,DRP项目视频是首选,累计下载量已经达到上千万,很多同学通过自学该视频找到了软件开发工作. 000_00_动力节点_王勇_Java项目视频_DRP完整版-视频观看说明 000_01_动力节点_王勇_Java项目视频_DRP完整版_资料_DRP所有版本及示例源代码

  • Vue完整版和runtime版的区别详解

    目录 创建Vue实例的三种方式 从HTML得到视图 用JS构建视图 使用vue-loader 两者对比 最佳实践 SEO友好 创建Vue实例的三种方式 从HTML得到视图 前提:使用完整版,CDN引入或者修改配置 const { defineConfig } = require('@vue/cli-service') module.exports = defineConfig({ // ... configureWebpack: { resolve: { alias: { vue$: 'vue/

  • WebGame《逆转裁判》完整版 代码下载(1月24日更新)

    特别提醒:您可以自由下载并更改代码,欢迎所有有志于WebGame领域的朋友给我写信或到我的Blog上留言. 演示地址:http://nzcp.gbq.cn/screen.width*0.7) {this.resized=true; this.width=screen.width*0.7; this.style.cursor='hand'; this.alt='Click here to open new window\nCTRL+Mouse wheel to zoom in/out';}" on

  • Mybatis Generator最完美配置文件详解(完整版)

    最近没做项目,重新整理了一个最完整的Mybatis Generator(简称MBG)的最完整配置文件,带详解,再也不用去看EN的User Guide了: <?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE generatorConfiguration PUBLIC "-//mybatis.org//DTD MyBatis Generator Configuration 1.0//EN"

  • Mysql命令大全(完整版)

    一.连接数据库 格式:mysql -h主机地址 -u用户名 -p用户密码 1.1.连接到本机上的MYSQL. 首先打开DOS窗口,然后进入目录mysql\bin,再键入命令mysql -u root -p,回车后提示你输密码. 注意用户名前可以有空格也可以没有空格,但是密码前必须没有空格,否则让你重新输入密码. 如果刚安装好MYSQL,超级用户root是没有密码的,故直接回车即可进入到MYSQL中了,MYSQL的提示符是: mysql> 1.2连接到远程主机上的MYSQL. 假设远程主机的IP为

随机推荐