iis服务器助手广告广告
返回顶部
首页 > 资讯 > 前端开发 > node.js >Nodejs v14源码分析之如何使用Event模块
  • 622
分享到

Nodejs v14源码分析之如何使用Event模块

2024-04-02 19:04:59 622人浏览 薄情痞子
摘要

本篇内容主要讲解“nodejs v14源码分析之如何使用Event模块”,感兴趣的朋友不妨来看看。本文介绍的方法操作简单快捷,实用性强。下面就让小编来带大家学习“nodejs v14源码分析之如何使用Eve

本篇内容主要讲解“nodejs v14源码分析之如何使用Event模块”,感兴趣的朋友不妨来看看。本文介绍的方法操作简单快捷,实用性强。下面就让小编来带大家学习nodejs v14源码分析之如何使用Event模块”吧!

events模块是node.js中比较简单但是却非常核心的模块,Node.js中,很多模块都继承于events模块,events模块是发布、订阅模式的实现。我们首先看一下如何使用events模块。

const { EventEmitter } = require('events');   class Events extends EventEmitter {}   const events = new Events();   events.on('demo', () => {       console.log('emit demo event');   });   events.emit('demo');

接下来我们看一下events模块的具体实现。

1 初始化 当new一个EventEmitter或者它的子类时,就会进入EventEmitter的逻辑。

function EventEmitter(opts) {     EventEmitter.init.call(this, opts);   }      EventEmitter.init = function(opts) {     // 如果是未初始化或者没有自定义_events,则初始化     if (this._events === undefined ||         this._events === ObjectGetPrototypeOf(this)._events) {        this._events = ObjectCreate(null);        this._eventsCount = 0;      }            this._maxListeners = this._maxListeners || undefined;          // 是否开启捕获promise reject,默认false      if (opts && opts.captureRejections) {        this[kCapture] = Boolean(opts.captureRejections);      } else {        this[kCapture] = EventEmitter.prototype[kCapture];      }    };

EventEmitter的初始化主要是初始化了一些数据结构和属性。唯一支持的一个参数就是captureRejections,captureRejections表示当触发事件,执行处理函数时,EventEmitter是否捕获处理函数中的异常。后面我们会详细讲解。

2 订阅事件  初始化完EventEmitter之后,我们就可以开始使用订阅、发布的功能。我们可以通过addListener、prependListener、on、once订阅事件。addListener和on是等价的,prependListener的区别在于处理函数会被插入到队首,而默认是追加到队尾。once注册的处理函数,最多被执行一次。四个api都是通过_addListener函数实现的。下面我们看一下具体实现。

function _addListener(target, type, listener, prepend) {     let m;     let events;     let existing;     events = target._events;     // 还没有初始化_events则初始化,_eventsCount为事件类型个数     if (events === undefined) {       events = target._events = ObjectCreate(null);       target._eventsCount = 0;      } else {                if (events.newListener !== undefined) {          target.emit('newListener',                       type,                      listener.listener ?                       listener.listener :                       listener);          // newListener处理函数可能会修改_events,这里重新赋值          events = target._events;        }        // 判断是否已经存在处理函数        existing = events[type];      }      // 不存在则以函数的形式存储,否则以数组形式存储      if (existing === undefined) {        events[type] = listener;        // 新增一个事件类型,事件类型个数加一      ++target._eventsCount;      } else {              if (typeof existing === 'function') {          existing = events[type] =            prepend ? [listener, existing] : [existing, listener];        } else if (prepend) {          existing.unshift(listener);        } else {          existing.push(listener);        }            // 处理告警,处理函数过多可能是因为之前的没有删除,造成内存泄漏        m = _getMaxListeners(target);        // 该事件处理函数达到阈值并且还没有提示过警告信息则提示      if (m > 0 && existing.length > m && !existing.warned) {          existing.warned = true;          const w = new Error('错误信息…');          w.name = 'MaxListenersExceededWarning';          w.emitter = target;          w.type = type;          w.count = existing.length;          process.emitWarning(w);        }      }          return target;    }

接下来我们看一下once的实现,对比其它几种api,once的实现相对比较复杂,因为我们要控制处理函数最多执行一次,所以我们需要保证在事件触发的时候,执行用户定义函数的同时,还需要删除注册的事件。

ventEmitter.prototype.once = function once(type, listener) {    this.on(type, _onceWrap(this, type, listener));    return this;   ;     unction onceWrapper() {    // 还没有触发过    if (!this.fired) {      // 删除它       this.target.removeListener(this.type, this.wrapFn);       // 触发了       this.fired = true;       // 执行       if (arguments.length === 0)         return this.listener.call(this.target);       return this.listener.apply(this.target, arguments);     }   }   // 支持once api   function _onceWrap(target, type, listener) {     // fired是否已执行处理函数,wrapFn包裹listener的函数     const state = { fired: false, wrapFn: undefined, target, type, listener };     // 生成一个包裹listener的函数     const wrapped = onceWrapper.bind(state);        wrapped.listener = listener;     // 保存包裹函数,用于执行完后删除,见onceWrapper     state.wrapFn = wrapped;     return wrapped;   }

Once函数构造一个上下文(state)保存用户处理函数和执行状态等信息,然后通过bind返回一个带有该上下文(state)的函数wrapped注册到事件系统。当事件触发时,在wrapped函数中首先移除wrapped,然后执行用户的函数。Wrapped起到了劫持的作用。另外还需要在wrapped上保存用户传进来的函数,当用户在事件触发前删除该事件时或解除该函数时,在遍历该类事件的处理函数过程中,可以通过wrapped.listener找到对应的项进行删除。

3 触发事件 分析完事件的订阅,接着我们看一下事件的触发。

EventEmitter.prototype.emit = function emit(type, ...args) {     // 触发的事件是否是error,error事件需要特殊处理     let doError = (type === 'error');        const events = this._events;     // 定义了处理函数(不一定是type事件的处理函数)     if (events !== undefined) {                if (doError && events[kErrORMonitor] !== undefined)          this.emit(kErrorMonitor, ...args);        // 触发的是error事件但是没有定义处理函数        doError = (doError && events.error === undefined);      } else if (!doError)       // 没有定义处理函数并且触发的不是error事件则不需要处理,        return false;          // If there is no 'error' event listener then throw.      // 触发的是error事件,但是没有定义处理error事件的函数,则报错      if (doError) {        let er;        if (args.length > 0)          er = args[0];        // 第一个入参是Error的实例        if (er instanceof Error) {          try {            const capture = {};                        Error.captureStackTrace(capture, EventEmitter.prototype.emit);            ObjectDefineProperty(er, kEnhanceStackBeforeInspector, {              value: enhanceStackTrace.bind(this, er, capture),              configurable: true            });          } catch {}          throw er; // Unhandled 'error' event        }            let stringifiedEr;        const { inspect } = require('internal/util/inspect');        try {          stringifiedEr = inspect(er);        } catch {          stringifiedEr = er;        }        const err = new ERR_UNHANDLED_ERROR(stringifiedEr);        err.context = er;        throw err; // Unhandled 'error' event      }      // 获取type事件对应的处理函数      const handler = events[type];      // 没有则不处理      if (handler === undefined)        return false;      // 等于函数说明只有一个      if (typeof handler === 'function') {        // 直接执行        const result = ReflectApply(handler, this, args);        // 非空判断是不是promise并且是否需要处理,见addCatch        if (result !== undefined && result !== null) {          addCatch(this, result, type, args);        }      } else {        // 多个处理函数,同上        const len = handler.length;        const listeners = arrayClone(handler, len);        for (let i = 0; i < len; ++i) {          const result = ReflectApply(listeners[i], this, args);          if (result !== undefined && result !== null) {            addCatch(this, result, type, args);          }        }      }          return true;    }

我们看到在Node.js中,对于error事件是特殊处理的,如果用户没有注册error事件的处理函数,可能会导致程序挂掉,另外我们看到有一个addCatch的逻辑,addCatch是为了支持事件处理函数为异步模式的情况,比如async函数或者返回Promise的函数。

function addCatch(that, promise, type, args) {     // 没有开启捕获则不需要处理     if (!that[kCapture]) {       return;     }     // that throws on second use.     try {       const then = promise.then;           if (typeof then === 'function') {          // 注册reject的处理函数          then.call(promise, undefined, function(err) {            process.nextTick(emitUnhandledRejectionOrErr, that, err, type, args);          });        }      } catch (err) {        that.emit('error', err);      }    }        function emitUnhandledRejectionOrErr(ee, err, type, args) {      // 用户实现了kRejection则执行      if (typeof ee[kRejection] === 'function') {        ee[kRejection](err, type, ...args);      } else {        // 保存当前值        const prev = ee[kCapture];        try {                    ee[kCapture] = false;          ee.emit('error', err);        } finally {          ee[kCapture] = prev;        }      }    }

4 取消订阅 我们接着看一下删除事件处理函数的逻辑。

function removeAllListeners(type) {         const events = this._events;         if (events === undefined)           return this;                    if (events.removeListener === undefined) {            // 等于0说明是删除全部            if (arguments.length === 0) {              this._events = ObjectCreate(null);              this._eventsCount = 0;            } else if (events[type] !== undefined) {                          if (--this._eventsCount === 0)                this._events = ObjectCreate(null);              else                delete events[type];            }            return this;          }                      if (arguments.length === 0) {                        for (const key of ObjecTKEys(events)) {              if (key === 'removeListener') continue;              this.removeAllListeners(key);            }            // 这里删除removeListener事件,见下面的逻辑            this.removeAllListeners('removeListener');            // 重置数据结构            this._events = ObjectCreate(null);            this._eventsCount = 0;            return this;          }          // 删除某类型事件          const listeners = events[type];              if (typeof listeners === 'function') {            this.removeListener(type, listeners);          } else if (listeners !== undefined) {            // LIFO order            for (let i = listeners.length - 1; i >= 0; i--) {              this.removeListener(type, listeners[i]);            }          }              return this;        }

removeAllListeners函数主要的逻辑有两点,第一个是removeListener事件需要特殊处理,这类似一个钩子,每次用户删除事件处理函数的时候都会触发该事件。第二是removeListener函数。removeListener是真正删除事件处理函数的实现。removeAllListeners是封装了removeListener的逻辑。

function removeListener(type, listener) {      let originalListener;      const events = this._events;      // 没有东西可删除      if (events === undefined)        return this;         const list = events[type];      // 同上       if (list === undefined)         return this;       // list是函数说明只有一个处理函数,否则是数组,如果list.listener === listener说明是once注册的       if (list === listener || list.listener === listener) {         // type类型的处理函数就一个,并且也没有注册其它类型的事件,则初始化_events         if (--this._eventsCount === 0)           this._events = ObjectCreate(null);         else {           // 就一个执行完删除type对应的属性           delete events[type];           // 注册了removeListener事件,则先注册removeListener事件           if (events.removeListener)             this.emit('removeListener',                       type,                       list.listener || listener);         }       } else if (typeof list !== 'function') {         // 多个处理函数         let position = -1;         // 找出需要删除的函数         for (let i = list.length - 1; i >= 0; i--) {           if (list[i] === listener ||               list[i].listener === listener) {             // 保存原处理函数,如果有的话             originalListener = list[i].listener;             position = i;             break;           }         }             if (position < 0)           return this;         // 第一个则出队,否则删除一个         if (position === 0)           list.shift();         else {           if (spliceOne === undefined)             spliceOne = require('internal/util').spliceOne;           spliceOne(list, position);         }         // 如果只剩下一个,则值改成函数类型         if (list.length === 1)           events[type] = list[0];         // 触发removeListener         if (events.removeListener !== undefined)           this.emit('removeListener',                      type,                     originalListener || listener);       }           return this;     };

到此,相信大家对“Nodejs v14源码分析之如何使用Event模块”有了更深的了解,不妨来实际操作一番吧!这里是编程网网站,更多相关内容可以进入相关频道进行查询,关注我们,继续学习!

--结束END--

本文标题: Nodejs v14源码分析之如何使用Event模块

本文链接: https://www.lsjlt.com/news/83615.html(转载时请注明来源链接)

有问题或投稿请发送至: 邮箱/279061341@qq.com    QQ/279061341

本篇文章演示代码以及资料文档资料下载

下载Word文档到电脑,方便收藏和打印~

下载Word文档
猜你喜欢
  • Nodejs v14源码分析之如何使用Event模块
    本篇内容主要讲解“Nodejs v14源码分析之如何使用Event模块”,感兴趣的朋友不妨来看看。本文介绍的方法操作简单快捷,实用性强。下面就让小编来带大家学习“Nodejs v14源码分析之如何使用Eve...
    99+
    2024-04-02
  • nodejs模块系统源码分析
    目录概述CommonJS 规范Node 对 CommonJS 规范的实现模块导出以及引用模块系统实现分析模块定位加载策略模块加载模块文件处理后缀处理编译执行概述 Node.js的出现...
    99+
    2024-04-02
  • python如何使用contextvars模块源码分析
    目录前记更新说明1.有无上下文传变量的区别2.如何使用contextvars模块3.如何优雅的使用contextvars4.contextvars的原理4.1 ContextMeta...
    99+
    2024-04-02
  • Mybatis源码分析之插件模块
    Mybatis插件模块 插件这个东西一般用的比较少,就算用的多的插件也算是PageHelper分页插件; PageHelper官网:https://github.com/pagehe...
    99+
    2024-04-02
  • muduo源码分析之TcpServer模块详细介绍
    这次我们开始muduo源代码的实际编写,首先我们知道muduo是LT模式,Reactor模式,下图为Reactor模式的流程图[来源1] 然后我们来看下muduo的整体架构[来源1...
    99+
    2024-04-02
  • 如何使用Nodejs-cluster模块
    这篇文章主要为大家展示了“如何使用Nodejs-cluster模块”,内容简而易懂,条理清晰,希望能够帮助大家解决疑惑,下面让小编带领大家一起研究并学习一下“如何使用Nodejs-cluster模块”这篇文章吧。基本用法Node.js默认单...
    99+
    2023-06-22
  • Nodejs中如何使用crypto模块
    本篇文章给大家分享的是有关Nodejs中如何使用crypto模块,小编觉得挺实用的,因此分享给大家学习,希望大家阅读完这篇文章后可以有所收获,话不多说,跟着小编一起来看看吧。crypto模块是nodejs的...
    99+
    2024-04-02
  • Nodejs中的模块系统该如何使用
    本篇文章给大家分享的是有关Nodejs中的模块系统该如何使用,小编觉得挺实用的,因此分享给大家学习,希望大家阅读完这篇文章后可以有所收获,话不多说,跟着小编一起来看看吧。模块化的背景早期 JavaScrip...
    99+
    2024-04-02
  • Redis源码分析之set 和 sorted set 使用
    目录set 和 sorted set前言set常见命令set 的使用场景看下源码实现sorted set常见的命令使用场景分析下源码实现总结参考set 和 sorted set 前言...
    99+
    2024-04-02
  • Nodejs中如何使用path路径处理模块
    这篇文章将为大家详细讲解有关Nodejs中如何使用path路径处理模块,文章内容质量较高,因此小编分享给大家做个参考,希望大家阅读完这篇文章后对相关知识有一定的了解。获取路径/文件名/扩展名获取路径:pat...
    99+
    2024-04-02
  • Python基础之模块如何使用
    一、模块模块可以看成是一堆函数的集合体。一个py文件内部就可以放一堆函数,因此一个py文件就可以看成一个模块。如果这个py文件的文件名为module.py,模块名则是module。1、模块的四种形式在Python中,总共有以下四种形式的模块...
    99+
    2023-05-15
    Python
  • 如何用源代码分析FileZilla
    这期内容当中小编将会给大家带来有关如何用源代码分析FileZilla,文章内容丰富且以专业的角度为大家分析和叙述,阅读完这篇文章希望大家可以有所收获。FileZilla是一种快速、可信赖的FTP客户端以及服务器端开放源代码程式,具有多种特色...
    99+
    2023-06-16
  • 如何在Nodejs中使用模块fs文件系统
    目录概述文件描述符同步、异步与Promise同步写法异步写法(推荐)Promise写法目录与目录项文件信息ReadStream与WriteStream概述 node 的fs文档密密麻...
    99+
    2024-04-02
  • Nodejs中如何使用string_decoder模块将buffer转成string
    小编给大家分享一下Nodejs中如何使用string_decoder模块将buffer转成string,相信大部分人都还不怎么了解,因此分享这篇文章给大家参考一下,希望大家阅读完这篇文章后大有收获,下面让我们一起去了解一下吧!本篇文章给大家...
    99+
    2023-06-15
  • 如何分析Saltstack常用模块及API
    这篇文章主要为大家分析了如何分析Saltstack常用模块及API的相关知识点,内容详细易懂,操作细节合理,具有一定参考价值。如果感兴趣的话,不妨跟着跟随小编一起来看看,下面跟着小编一起深入学习“如何分析Saltstack常用模块及API”...
    99+
    2023-06-05
  • 如何用jQuery 2.0.3源码分析Deferred
    本篇文章给大家分享的是有关如何用jQuery 2.0.3源码分析Deferred,小编觉得挺实用的,因此分享给大家学习,希望大家阅读完这篇文章后可以有所收获,话不多说,跟着小编一起来看看吧。构建Deferr...
    99+
    2024-04-02
  • nodejs中如何使用node-xlsx模块读取excel数据
    这篇文章给大家分享的是有关nodejs中如何使用node-xlsx模块读取excel数据的内容。小编觉得挺实用的,因此分享给大家做个参考,一起跟随小编过来看看吧。1.安装node-xlsxnode-xlsx模块 用于读取xlsx文件中的内容...
    99+
    2023-06-14
  • AngularJS中使用模块组织代码的示例分析
    本篇文章给大家分享的是有关AngularJS中使用模块组织代码的示例分析,小编觉得挺实用的,因此分享给大家学习,希望大家阅读完这篇文章后可以有所收获,话不多说,跟着小编一起来看看吧。下载 modu...
    99+
    2024-04-02
  • Spring注解之@Import注解的使用和源码分析
    目录介绍@Import导入bean的三种方式普通类ImportSelector接口ImportBeanDefinitionRegistrar接口源码解析总结介绍 今天主要介...
    99+
    2023-05-16
    Spring注解@Import Spring注解 @Import注解的使用 注解@Import源码
  • 如何用源码分析Java HashMap实例
    本篇文章为大家展示了如何用源码分析Java HashMap实例,内容简明扼要并且容易理解,绝对能使你眼前一亮,通过这篇文章的详细介绍希望你能有所收获。引言HashMap在键值对存储中被经常使用,那么它到底是如何实现键值存储的呢?一 Entr...
    99+
    2023-06-17
软考高级职称资格查询
编程网,编程工程师的家园,是目前国内优秀的开源技术社区之一,形成了由开源软件库、代码分享、资讯、协作翻译、讨论区和博客等几大频道内容,为IT开发者提供了一个发现、使用、并交流开源技术的平台。
  • 官方手机版

  • 微信公众号

  • 商务合作