Node 入门(7):events 模块和发布订阅模式

简介: 本文会介绍 events 模块的主要作用和使用方式,以及自己实现一个简单的发布订阅模式,帮助加深理解。

前言

本文会介绍 events 模块的主要作用和使用方式,以及自己实现一个简单的发布订阅模式,帮助加深理解。

events 模块

文档地址,点击访问

Node.js 是基于事件驱动实现的异步操作。 事件驱动依赖就是的 events 模块。

events 模块导出一个 EventEmitter 类,它是发布订阅模式的一种实现。

无论你是了解发布订阅模式,只要你有过前端开发的经验,那么对于 DOM事件监听,事件解绑等操作一定不陌生。其实这就是一种发布订阅。

平时开发中我们很少会直接使用这个模块。这是因为 Node.js 的很多内置模块,比如 fs , http 等模块,都是继承自 EventEmitter 类而实现的。

也就是说,我们平时经常用到的模块,就已经具备了发布订阅(事件监听、事件触发)的能力。后面会介绍到 Stream 和 http 模块的使用,到时候就会发现,到处都有发布订阅的影子。

EventEmitter 类

events 模块对外暴露 EventEmitter 类。

打印一下它的原型对象:

const EventEmitter = require('events')

console.log(EventEmitter.prototype)

有很多方法,光看名字我们就能知道每个方法的作用是什么:

  • on:订阅事件
  • emit:发布事件
  • once:只订阅一次,也就是只能触发一次
  • removeListener:移除订阅者
  • removerAllListeners:移除所有的订阅者
  • off:removeLister 方法的别名

使用

EventEmitter 类的使用方式有两种。

首先它是一个构造函数(源码中以构造函数实现),所以可以通过实例化的方法来使用。

其次,node 的很多模块都是继承自它,所以子类除了自身的能力之外,也具备了发布订阅的能力。

这里演示下第二种。

const EventEmitter = require('events')


class Reader extends EventEmitter {
  // ... 
}

const reader = new Reader()

const buy = bookName => {
  console.log('买新书:', bookName)
}

// 订阅小册上新的事件;当有上新后,就买买买
reader.on('new', buy)

// 小册上新了
reader.emit('new', '《TypeScript 全面进阶指南》') // 买新书: 《TypeScript 全面进阶指南》
// 小册又上新了
reader.emit('new', '《NestJS 项目实战》') // 买新书: 《《NestJS 项目实战》》

// 最近不打算买书了: 取消订阅
reader.off('new', buy)

// 小册上新了。但是取消了订阅,所以消息不会发给你了
reader.emit('new', 'React 上天入地》') // 没有效果

实现发布订阅

实现发布订阅,也是常考的一道面试题。

实现起来也并不复杂,结合注释很方便理解。

// 定义一个构造函数
function EventEmitter() {
  //  存储订阅者,也就是事件处理函数的容器。
  // 数据结构为:{ event_type1: [handler1, ...], ... }
  this._events = Object.create(null) // 
}

// 订阅事件的方法
EventEmitter.prototype.on = function on(type, handler) {
  // 首先判断要注册的事件对应的容器是否存在,若存在则将新的 handler 存入,否则先创建一个对应的容器
  let events = this._events[type]
  if (!events) {
    events = this._events[type] = []
  }

  events.push(handler)
}

// 只订阅一次事件的方法
// 思路:利用切片编程的思想,给原始 handler 包装一层。
// 正常的执行逻辑:handler 执行
// 切片编程的逻辑:handler 执行,执行完再调用一次 off 。相当于扩展了 handler 方法
EventEmitter.prototype.once = function on(type, handler) {
  const fn = (...args) => {
    handler(...args)
    this.off(type, fn)
  }

  this.on(type, fn)
}

// 发布事件的方法
EventEmitter.prototype.emit = function emit(type, ...args) {
  // 从事件容器中取出对应的 handler 依次去执行
  // 若发布的事件不存在,则不进行处理
  let events = this._events[type]
  // events可能存在空数组的情况,需要处理
  if (!events || events.length === 0) {
    return
  }
  events.forEach(handler => handler(...args))
}


// 移除事件监听
EventEmitter.prototype.off = function off(type, handler) {
  let events = this._events[type]
  if (!events || events.length === 0) {
    return
  }

  // 过滤掉容器中需要解除监听的方法
  this._events[type] = events.filter(item => handler !== item)
}

// 测试
const emitter = new EventEmitter()

const play = (...args) => {
  console.log('play', ...args)
}

const sing = (...args) => {
  console.log('sing', ...args)
}

emitter.on('play', play)
emitter.on('sing', sing)


emitter.emit('sing', 'rap')  // sing rap
emitter.emit('play', '篮球')  // play 篮球

emitter.off('play', play)
emitter.emit('play')


const dance = () => {
  console.log('dance')
}

emitter.once('dance', dance)

emitter.emit('dance') // dance
emitter.emit('dance') // 已移除订阅,不执行

延申阅读:eventbus 和 mitt

有一道经典的面试题:Vue 组件通信的方式有哪些?

除了常见的通过属性,自定义事件,借助状态库,在 Vue2 中还有一种方式,叫作 eventBus 事件总线。

EventBus 本质上就是一个发布订阅模式。但是在 Vue3 中被移除了,官方推荐使用一个第三方库 mitt 来做发布订阅。

Mitt是一个十分小巧的发布订阅库,大约只有200字节左右。而且用法上也没有什么太大的差异,稍微看一下文档就会用了。

import mitt from 'mitt'

const emitter = mitt()

// 订阅事件
emitter.on('foo', e => console.log('foo', e) )

// 订阅所有的事件
emitter.on('*', (type, e) => console.log(type, e) )

// 触发事件
emitter.emit('foo', { a: 'b' })

// 清除所有的订阅者
emitter.all.clear()

// 使用事件处理函数的引用,方便移除监听 
function onFoo() {}
emitter.on('foo', onFoo)   // listen
emitter.off('foo', onFoo)  // unlisten

总结

本文介绍了 Node.js 中 events 模块的使用,它主要导出一个 EventEmitter 类来做发布订阅。Node.js 的很多核心模块都继承自 EventEmitter 类。

它是一个底层的模块,但通常很少直接使用它。等后面讲到 Stream,再后面讲到 http 模块的时候,就能体会到它的存在了。

目录
相关文章
|
1月前
|
缓存 JavaScript 安全
nodejs里面的http模块介绍和使用
综上所述,Node.js的http模块是构建Web服务的基础,其灵活性和强大功能,结合Node.js异步非阻塞的特点,为现代Web应用开发提供了坚实的基础。
102 62
|
17天前
|
数据采集 存储 JavaScript
如何使用Puppeteer和Node.js爬取大学招生数据:入门指南
本文介绍了如何使用Puppeteer和Node.js爬取大学招生数据,并通过代理IP提升爬取的稳定性和效率。Puppeteer作为一个强大的Node.js库,能够模拟真实浏览器访问,支持JavaScript渲染,适合复杂的爬取任务。文章详细讲解了安装Puppeteer、配置代理IP、实现爬虫代码的步骤,并提供了代码示例。此外,还给出了注意事项和优化建议,帮助读者高效地抓取和分析招生数据。
如何使用Puppeteer和Node.js爬取大学招生数据:入门指南
|
2月前
|
JavaScript 前端开发
Vue、ElementUI配合Node、multiparty模块实现图片上传并反显_小demo
如何使用Vue和Element UI配合Node.js及multiparty模块实现图片上传并反显的功能,包括前端的Element UI组件配置和后端的Node.js服务端代码实现。
34 1
Vue、ElementUI配合Node、multiparty模块实现图片上传并反显_小demo
|
1月前
|
JavaScript 前端开发 API
探索Node.js中的异步编程模式
【10月更文挑战第4天】在JavaScript的世界中,异步编程是提升应用性能和用户体验的关键。本文将深入探讨Node.js中异步编程的几种模式,包括回调函数、Promises、async/await,并分享如何有效利用这些模式来构建高性能的后端服务。
|
1月前
|
缓存 JSON JavaScript
Node.js模块系统
10月更文挑战第4天
37 2
|
1月前
|
JavaScript 前端开发 调度
探索Node.js中的异步编程模式
在Node.js的世界里,异步编程是核心。本文将带你深入了解异步编程的精髓,通过代码示例和实际案例分析,我们将一起掌握事件循环、回调函数、Promises以及async/await等关键概念。准备好迎接挑战,让你的Node.js应用飞起来!
|
1月前
|
JavaScript 前端开发 开发者
探索Node.js中的异步编程模式
【9月更文挑战第33天】在JavaScript的后端领域,Node.js凭借其非阻塞I/O和事件驱动的特性,成为高性能应用的首选平台。本文将深入浅出地探讨Node.js中异步编程的核心概念、Promise对象、Async/Await语法以及它们如何优化后端开发的效率和性能。
25 7
|
1月前
|
JavaScript 应用服务中间件 Apache
Node.js Web 模块
10月更文挑战第7天
30 0
|
1月前
|
JavaScript 网络协议
Node.js 工具模块
10月更文挑战第7天
20 0
|
1月前
|
Web App开发 JSON JavaScript
深入浅出:Node.js后端开发入门与实践
【10月更文挑战第4天】在这个数字信息爆炸的时代,了解如何构建一个高效、稳定的后端系统对于开发者来说至关重要。本文将引导你步入Node.js的世界,通过浅显易懂的语言和逐步深入的内容组织,让你不仅理解Node.js的基本概念,还能掌握如何使用它来构建一个简单的后端服务。从安装Node.js到实现一个“Hello World”程序,再到处理HTTP请求,文章将带你一步步走进Node.js的大门。无论你是初学者还是有一定经验的开发者,这篇文章都将为你打开一扇通往后端开发新世界的大门。