39 # events 模块的实现原理

简介: 39 # events 模块的实现原理

观察者模式:会有两个类,观察者会被存储到被观察者中,如果被观察者状态变化,会主动通知观察者,调用观察者的更新方法

发布订阅好处:可以解耦合

const EventEmitter = require("events");
// 使用自己实现的 events 类
// const EventEmitter = require("./39/events.js");
const util = require("util");
function Man() {}
// 继承
util.inherits(Man, EventEmitter);
// 创建实例
let man = new Man();
// 发布订阅
const sleep = (name) => console.log(name, "睡");
const eat = (name) => console.log(name, "吃");

newListener 固定写法,每次绑定都会触发,可以用于判断监听了哪些事件:https://nodejs.org/dist/latest-v18.x/docs/api/events.html#event-newlistener

man.on("newListener", (type) => {
    console.log("newListener---->", type);
    if (type === "唱跳rap篮球") {
        // 在当前同步代码执行完毕后触发事件
        process.nextTick(() => {
            man.emit(type, "小坤");
        });
    }
});
// newListener 测试
man.on("唱跳rap篮球", sleep);
man.once("唱跳rap篮球", eat);

on 方法

man.on("唱跳rap篮球", sleep);
man.on("唱跳rap篮球", eat);
man.emit("唱跳rap篮球", "小坤");

off 方法

man.on("唱跳rap篮球", sleep);
man.off("唱跳rap篮球", sleep);
man.emit("唱跳rap篮球", "小坤");

once 方法

man.once("唱跳rap篮球", eat);
man.emit("唱跳rap篮球", "小坤");
man.emit("唱跳rap篮球", "小坤");

自己实现一个简单版本的 EventEmitter

下面实现一下上面提的五种方法:newListener、on、emit、off、once

function EventEmitter() {
    this._events = {}; // 默认给 EventEmitter 准备的
}
// 订阅
EventEmitter.prototype.on = function (eventName, callback) {
    if (!this._events) this._events = {};
    // 如果不是 newListener 那就需要触发 newListener 的回调
    if (eventName !== "newListener") {
        this.emit("newListener", eventName);
    }
    if (!this._events[eventName]) this._events[eventName] = [];
    this._events[eventName].push(callback);
};
// 发布
EventEmitter.prototype.emit = function (eventName, ...args) {
    if (this._events && this._events[eventName]) {
        this._events[eventName].forEach((event) => event(...args));
    }
};
// 注销
EventEmitter.prototype.off = function (eventName, callback) {
    if (this._events && this._events[eventName]) {
        // 这里需要对 once 里的进行处理:删除时获取 once 里的 l 属性和 callback 比较,如果相等也需要删除
        this._events[eventName] = this._events[eventName].filter((cb) => cb != callback && cb.l != callback);
    }
};
// 订阅只执行一次
EventEmitter.prototype.once = function (eventName, callback) {
    // 使用切片
    const once = (...args) => {
        callback(...args);
        this.off(eventName, once);
    };
    // 给 once 添加 l 属性用于表示 callback
    once.l = callback;
    // 先绑定一个一次性事件,稍后触发时在将事件清空
    this.on(eventName, once);
};
module.exports = EventEmitter;

events 源码快览

先配置 lanch.json

{
    // 使用 IntelliSense 了解相关属性。 
    // 悬停以查看现有属性的描述。
    // 欲了解更多信息,请访问: https://go.microsoft.com/fwlink/?linkid=830387
    "version": "0.2.0",
    "configurations": [
        {
            "type": "node",
            "request": "launch",
            "name": "启动程序",
            // 这里我们不跳过 node 的内部源码,因为等下需要调试 require,需注释掉
            "skipFiles": [
                // "<node_internals>/**"
            ],
            "program": "${workspaceFolder}\\doc\\39 # events 模块的实现原理.js"
        }
    ]
}

然后调用方法处打上断点,进入单步调试

on 方法:别名 addListener 底层调用的是 _addListener

源码这里面写了 Object.create(null),它跟 {} 有区别,它没有原型链,而 {} 是有原型链的。

newListener 方法:我们可以看到在 _addListener 里面有对 newListener 做处理,会先触发 newListener。

emit 方法:

这里需要提到一个 primordials :(primordials 变量是nodejs内部对原生 js 众多类型构造器、方法的一个外观,防止当原生 js 构造器、方法被覆盖时导致出错)这里的 apply 就是来自 primordials 里的 Reflect.apply

off 方法:别名 removeListener

也是类似的判断方法:list[i] === listener || list[i].listener === listener

once 方法:底层调用了 _onceWrap,将 listener 挂在了 wrapped.listener 上,我们自己实现的是挂在 once.l

目录
相关文章
|
6月前
|
Java Spring
@Async 的实现原理是什么?
【8月更文挑战第17天】@Async 的实现原理是什么?
130 3
|
4月前
|
Java
Optional源码分析(涉及Objects源码和Stream源码)
本文分析了Java中Optional类的源码,包括其内部的Objects.requireNonNull方法、EMPTY定义、构造方法、ofNullable方法、isEmpty方法以及如何与Stream类交互,展示了Optional类如何避免空指针异常并提供流式操作。
51 0
Optional源码分析(涉及Objects源码和Stream源码)
|
5月前
|
JavaScript 前端开发 开发者
探索yocto-queue库:替代数组的实现原理与方法
在需要高性能队列结构的场景下,yocto-queue提供了一个轻量级且高效的解决方案。它的实现原理优雅且有效,使得在实际应用中,特别是在性能敏感的环境下,成为了数组的一个强大替代者。通过减少性能开销,yocto-queue使得JavaScript开发者能够构建更快、更可靠的应用程序,从而提高用户体验和应用性能。
63 2
|
5月前
Nest.js 实战 (十二):优雅地使用事件发布/订阅模块 Event Emitter
这篇文章介绍了在Nest.js构建应用时,如何通过事件/发布-订阅模式使应用程序更健壮、灵活、易于扩展,并简化服务间通信。文章主要围绕@nestjs/event-emitter模块展开,这是一个基于eventemitter2库的社区模块,提供了事件发布/订阅功能,使得实现事件驱动架构变得简单。文章还介绍了如何使用该模块,包括安装依赖、初始化模块、注册EventEmitterModule、使用装饰器简化监听等。最后总结,集成@nestjs/event-emitter模块可以提升应用程序的事件驱动能力,构建出更为松耦合、易扩展且高度灵活的系统架构,是构建现代、响应迅速且具有高度解耦特性的Nest.
|
7月前
|
JavaScript 前端开发 API
js 运行机制(含异步机制、同步任务、异步任务、宏任务、微任务、Event Loop)
js 运行机制(含异步机制、同步任务、异步任务、宏任务、微任务、Event Loop)
77 0
|
9月前
|
Java
Java多线程基础-7:wait() 和 notify() 用法解析
这篇内容探讨了Java中的`wait()`和`notify()`方法在多线程编程中的使用。
63 0
|
JavaScript
热点面试题:JS 中 call, apply, bind 概念、用法、区别及实现?
热点面试题:JS 中 call, apply, bind 概念、用法、区别及实现?
|
SQL 存储 缓存
重学Node系列02-异步实现与事件驱动
Node异步实现与事件驱动 这是重新阅读《深入浅出NodeJS》的相关笔记,这次阅读发现自己依旧收获很多,而第一次阅读的东西也差不多忘记完了,所以想着这次过一遍脑子,用自己的理解输出一下,方便记忆以及以后回忆...
99 0
|
JavaScript 前端开发
Node 入门(7):events 模块和发布订阅模式
本文会介绍 events 模块的主要作用和使用方式,以及自己实现一个简单的发布订阅模式,帮助加深理解。
437 0
|
存储 JavaScript 前端开发
企业级项目开发中的交互式解释器以及global全局定义、Stream流的合理运用和实战【Note.js】
企业级项目开发中的交互式解释器以及global全局定义、Stream流的合理运用和实战【Note.js】

热门文章

最新文章