惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

V
Vulnerabilities – Threatpost
P
Proofpoint News Feed
The Hacker News
The Hacker News
Know Your Adversary
Know Your Adversary
Threat Intelligence Blog | Flashpoint
Threat Intelligence Blog | Flashpoint
T
Tenable Blog
AWS News Blog
AWS News Blog
S
Securelist
T
Threatpost
C
Cybersecurity and Infrastructure Security Agency CISA
IT之家
IT之家
腾讯CDC
WordPress大学
WordPress大学
Spread Privacy
Spread Privacy
C
Check Point Blog
cs.CL updates on arXiv.org
cs.CL updates on arXiv.org
Engineering at Meta
Engineering at Meta
Latest news
Latest news
A
About on SuperTechFans
The Register - Security
The Register - Security
L
LINUX DO - 热门话题
T
The Exploit Database - CXSecurity.com
C
Cisco Blogs
T
Tailwind CSS Blog
Simon Willison's Weblog
Simon Willison's Weblog
阮一峰的网络日志
阮一峰的网络日志
MyScale Blog
MyScale Blog
大猫的无限游戏
大猫的无限游戏
T
Tor Project blog
L
Lohrmann on Cybersecurity
G
GRAHAM CLULEY
B
Blog RSS Feed
Scott Helme
Scott Helme
让小产品的独立变现更简单 - ezindie.com
让小产品的独立变现更简单 - ezindie.com
NISL@THU
NISL@THU
P
Privacy International News Feed
Security Latest
Security Latest
Recorded Future
Recorded Future
L
LangChain Blog
Cyberwarzone
Cyberwarzone
C
Cyber Attacks, Cyber Crime and Cyber Security
C
CXSECURITY Database RSS Feed - CXSecurity.com
博客园 - 聂微东
Google DeepMind News
Google DeepMind News
Last Week in AI
Last Week in AI
Apple Machine Learning Research
Apple Machine Learning Research
F
Fortinet All Blogs
O
OpenAI News
T
Threat Research - Cisco Blogs
Blog — PlanetScale
Blog — PlanetScale

蚊子的前端博客

微说 | 有的人觉得只要不考XX,一定能考好!-蚊子的前端博客 微说 | 春天来了-蚊子的前端博客 明天和意外不知道哪个先来-蚊子的前端博客 微说 | 既然错过了路口,就及时止损,重新规划路线上路-蚊子的前端博客 微说 | 2025年的出生人口数是792万-蚊子的前端博客 微说 | 工作年终总结,让写对接的接口的数量?-蚊子的前端博客 微说 | 温家宝:没有政治体制改革的成功 经济体制改革不可能进行到底-蚊子的前端博客 微说 | 违法犯罪了该不该被禁言?-蚊子的前端博客 微说 | 不明白为什么在推荐去俄罗斯旅游-蚊子的前端博客 微说 | 易中天论骗子-蚊子的前端博客 微说 | 一场大风吹散了秋天-蚊子的前端博客 微说 | 既要、又要、还要、更要!-蚊子的前端博客 又是一年的国庆雨季-蚊子的前端博客 微说 | 预估下2025年的出生人口数据-蚊子的前端博客 微说 | 可惜了我那些小时候的书本-蚊子的前端博客 微说 | 牛马有的是,驴不够了!-蚊子的前端博客 微说 | 能否有一条非户口也能高考的路-蚊子的前端博客 微说 | 小聊中医-蚊子的前端博客 微说 | 将要制作的一款新产品-蚊子的前端博客 微说 | 做人要有信-蚊子的前端博客 微说 | 好一个正义联盟-蚊子的前端博客 微说 | 都是见过吃过的主儿-蚊子的前端博客 微说 | 追求8小时工作制有错吗?-蚊子的前端博客 微说 | 如果尖锐的批评完全消失-蚊子的前端博客 微说 | 很好!-蚊子的前端博客 微说 | 封禁用户可以,但要告知具体原因-蚊子的前端博客 微说 | 面朝大海,春暖花开-蚊子的前端博客 微说 | 程序员的悲哀是什么?-蚊子的前端博客 微说 | 2025年出生人口的预测-蚊子的前端博客 前端在 LiveKit 中如何获取所有的参与者-蚊子的前端博客 在 Electron 中使用 LiveKit 实现屏幕共享-蚊子的前端博客 在 Electron 应用中,如何获取 Mac 的摄像头和麦克风权限-蚊子的前端博客 微说 | 不要总让台湾艺人表态-蚊子的前端博客 微说 | 他们总说要更开放-蚊子的前端博客 自己封装 uuid 踩的一个小坑-蚊子的前端博客 CKEditor5 光标在粘贴的图片下一行-蚊子的前端博客 软考出成绩了,没过-蚊子的前端博客 微说 | 也许有一天我们这个社会会进步-蚊子的前端博客 好久没有更新文章了-蚊子的前端博客 微说 | 中共中央关于进一步全面深化改革,推进中国式现代化的决定-蚊子的前端博客 微说 | 关于惩罚性赔偿-蚊子的前端博客 微说 | 生孩子是谁的事儿!-蚊子的前端博客 微说 | 给群众一些选择电视剧的空间-蚊子的前端博客 微说 | 活着-蚊子的前端博客 微说 | 借用睡前消息里面的一段话-蚊子的前端博客 微说 | 曾经梦寐以求的,现在却没了追求-蚊子的前端博客 微说 | 我小时候的那些书本-蚊子的前端博客 给博客添加了一个微说的功能-蚊子的前端博客 今年的双 11 很平静-蚊子的前端博客 微说 | 贪就是贪-蚊子的前端博客 微说 | 总有人说穿越小说为什么不发展工业和科技!-蚊子的前端博客 微说 | 花了很多时间和精力,-蚊子的前端博客 2025 年终于是增加了 2 天的假期-蚊子的前端博客 微说 | 这是我的第一条微说,-蚊子的前端博客 如何手写 Array 的 forEach 方法-蚊子的前端博客 看到聚美优品的没落,真是唏嘘-蚊子的前端博客 如何手写 Array 的 map 方法-蚊子的前端博客 秋意正浓时-蚊子的前端博客 收藏几个常用但访问比较慢的组件的官网网址-蚊子的前端博客 从 Axios 源码分析如何支持 fetch 方法的-蚊子的前端博客 1024,给博客评论区所有的头像戴上一顶可爱的帽子-蚊子的前端博客 使用 GitHub 搭建了一个静态博客-蚊子的前端博客 在 JavaScript 如何判断变量是否为空-蚊子的前端博客 我为什么还在坚持我的个人博客-蚊子的前端博客 不要再手动拼接 URL 参数,请使用 URLSearchParams-蚊子的前端博客 使用 NodeJs 向百度资源推送链接-蚊子的前端博客 工具链极度内卷,留给开发者的性能优化手段已经不多了-蚊子的前端博客 在 React 中显示多个标签,超出省略并可以 hover 显示更多-蚊子的前端博客 蚊子的前端博客:探索与分享前端技术的奇妙之旅-蚊子的前端博客 互联网程序员前景真的一片黑暗吗?-蚊子的前端博客 Deno2.0 正式发布,向 Node.js 兼容-蚊子的前端博客 React 请求数据别再使用 useEffect 和 useState,试试 SWR 吧!-蚊子的前端博客 React 的 useEffect 的一些使用场景和技巧-蚊子的前端博客 前端趣闻之 JavaScript 语言的诞生和发展-蚊子的前端博客 如何合并同一接口的相同参数的请求-蚊子的前端博客 百度搜索中关于蚊子前端博客的奇怪的检索数据-蚊子的前端博客 使用 React 实现 todo list 的 curd 操作-蚊子的前端博客 React 模板中为什么可以用逻辑与运算符-蚊子的前端博客 评论回复在一张表里的评论系统如何进行分页-蚊子的前端博客 React 组件多次调用时如何区分不同的 div 容器-蚊子的前端博客 安全赋值运算符,再也不用写 try-catch 了-蚊子的前端博客 使用 nextjs 重构我的个人博客-蚊子的前端博客 基于 gitlab 的 webhook 向飞书发通知-蚊子的前端博客 数字转换为更高量级单位的工具方法-蚊子的前端博客 如何避免旧请求的数据覆盖掉最新请求-蚊子的前端博客 JavaScript 中 toString 的冷知识-蚊子的前端博客 React 中 useState 和 useRef 与全局变量的区别-蚊子的前端博客 从面试官的角度分析下简历中存在的问题-蚊子的前端博客 使用 React 实现 6 个输入框的短信验证码功能-蚊子的前端博客 反驳那些要实时刷新页面的前端部署方案-蚊子的前端博客 JSON stringify 的一些不常见使用-蚊子的前端博客 JavaScript 中数组 Array 的常见操作-蚊子的前端博客 前端如何提升用户的交互体验-蚊子的前端博客 没有日的日期在iOS中报 Invalid Date 的探究-蚊子的前端博客 飞书很好,但还是没抗住-蚊子的前端博客 给 Antd 的 DatePicker 组件实现带有至今的功能-蚊子的前端博客 antd 的 InputNumber 输入框添加左右的加减按钮-蚊子的前端博客 给想进国央企的同学介绍一款招聘软件「国聘」-蚊子的前端博客 基于 React 和 antd 实现的图片裁剪压缩功能-蚊子的前端博客 uniapp 中 checkbox 中的 checked 不生效的方案-蚊子的前端博客
基于观察者模式实现一个EventEmitter类-蚊子的前端博客
author · 2019-05-08 · via 蚊子的前端博客

在实现一些基础类库时,我们是经常需要用到`EventEmitter`模块的,比如在微信中下载某个APP时,前端需要了解当前下载处在哪个状态:刚开始下载、在在下载中、下载的进度、下载已完成、下载失败等等,都是需要我们向发布事件的

我们在之前的文章中讲解中发布订阅者模式,不太了解这种模式的,可以先点击查看之前的文章浅谈javascript设计模式之发布订阅者模式。从发布订阅者模式的思想出发,来实现一个EventEmitter模块。

在实现一些基础类库时,我们是经常需要用到EventEmitter模块的,比如在微信中下载某个APP时,前端需要了解当前下载处在哪个状态:刚开始下载、在在下载中、下载的进度、下载已完成、下载失败等等,都是需要我们向发布事件的。那么对应的就是实现emiton

1. 简单的实现 #

一个简单的EventEmitter模块,是只至少要实现emit和on两个方法:

class EventEmitter {
    handles = {};
    on(evName, listener) {
        if (!this.handles[evName]) {
            this.handles[evName] = [];
        }
        this.handles[evName].push(listener);
    }

    emit(evName, ...args) {
        const handler = this.handles[evName];
        if (handler) {
            for(let i=0, len=handler.length; i<len; i++) {
                handler[i](...args);
            }
        }
    }
}

这样就完成了一个简单的EventEmitter基类,这个基类可以自己单独使用:

const downapp = new EventEmitter();
downapp.on('steps', step => console.log(step)); // 监听下载的进度

downapp.emit('steps', 0); // 输出: 0
downapp.emit('steps', 30); // 输出: 30
downapp.emit('steps', 100); // 输出; 100

也可以被其他类继承后使用:

class People extends EventEmitter {
    constructor() {
        super();
    }

    start() {
        let num = 10;
        let timer = setInterval(() => {
            this.emit('countdown', num--);
            if (num<=0) {
                clearInterval(timer);
            }
        }, 1000)
    }
}

const Tom = new People();
Tom.on('countdown', num => console.log('Tom', num));
Tom.start();

const Jerry = new People();
Jerry.on('countdown', num => console.log('Jerry', num));
Jerry.start();

2. 对EventEmitter扩展方法 #

基于emiton我们可以扩展出多个方法,方便使用:

方法名 说明
on(eventName, listener) 监听所有触发的事件
emit(eventName, ...args) 主动触发事件
once(eventName, listener) on事件类似,但只监听一次事件
off(eventName, listener) 移除某个事件
offAll([eventName]) 移除所有的事件,若传入具体的eventName,则移除该名称下所有的事件
newListener 有添加新的监听器时触发
removeListener 有监听器被移除时触发

2.1 off #

在如何实现once的方法时,必须得先知道事件是如何移除的:

class EventEmitter {
    off(evName, listener) {
        if (this.handles[evName]) {
            // 从数组中移除lisnter
            this.handles[evName] = this.handles[evName].filter(fn => fn!==listener);
        }
    }
}

// 接上面的People类
// const Tom = new People();
const listener = (num) => {
    console.log('Tom', num)
}
const listener2 = (num) => {
    console.log('Tom', num*num);
}
Tom.on('countdown', listener);
Tom.on('countdown', listener2);
Tom.on('countdown', listener);
Tom.off('countdown', listener); // 移除所有的listener
Tom.start(); // 只有listener有输出

通过Array.prototype.filter方法,过滤掉所有要移除的监听器。注意,跟为DOM添加和移除事件一样,不能传入匿名函数,例如下面的例子是不生效的,因为两个匿名函数虽然从函数体上看起来一样,但两者是不相等的:

Tom.on('countdown', (num) => {console.log(num)});
Tom.off('countdown', (num) => {console.log(num)}); // 移除
Tom.start(); // 依然有输出

2.2 once #

once,只要监听到第1个事件后,则立刻移除监听器,后续不再监听此事件:

Tom.once('countdown', (num) => {console.log(num)}); // 10
Tom.start();

countdown事件会从10每秒输出一个数字,至到0为止。但我们使用once后,只输出了一个10后,则不再有后续的输出,说明监听器已经被移除。

2.3 newListener和removeListener #

这两者都是表示监听器有变化时,程序主动触发的:

class EventEmitter {
    on(evName, listener) {
        if (!this.handles[evName]) {
            this.handles[evName] = [];
        }
        this.handles[evName].push(listener);

        // 有新增事件时,触发newListener
        this.emit('newListener', evName, listener);
        return this;
    }

    off(evName, listener) {
        if (this.handles[evName]) {
            // 从数组中移除lisnter
            this.handles[evName] = this.handles[evName].filter(fn => {
                if (fn!==listener) {
                    // 监听器有移除时,触发removeListener
                    this.emit('removeListener', evName, listener);
                    return true;
                }
                return false;
            })
        }
    }
}

那么这两个如何使用的呢,其实once这一个监听事件,就能触发上面的这两个监听器。先添加一个countdown监听器,执行一次监听后,马上移除countdown中的监听器:

const listener = (num) => {
    console.log('Tom', num)
}
Tom.on('newListener', (evName, listener) => 
    console.log('newListener', evName, listener)
);
Tom.on('removeListener', (evName, listener) => 
    console.log('removeListener', evName, listener)
);
Tom.once('countdown', listener);
Tom.start();

3. 完整的EventEmitter模块 #

我们从上面的章节中,已经了解了EventEmitter的原理,并实现了几个常用的模块。下面我们把完成的代码展示出来:

class EventEmitter {
    handles = {};
    on(evName, listener) {
        if (!this.handles[evName]) {
            this.handles[evName] = [];
        }
        this.handles[evName].push(listener);
        this.emit('newListener', evName, listener);
        return this;
    }

    once(evName, listener) {
        let fired = false;

        let magic = (...args) => {
            this.off(evName, magic);
            if (!fired) {
                fired = true;
                listener.apply(this, args);
            }
        }
        this.on(evName, magic);
        return this;
    }

    off(evName, listener) {
        if (this.handles[evName]) {
            // 从数组中移除lisnter
            this.handles[evName] = this.handles[evName].filter(fn => {
                if (fn!==listener) {
                    this.emit('removeListener', evName, listener)
                    return true;
                }
                return false;
            })
        }
    }

    emit(evName, ...args) {
        const handler = this.handles[evName];
        if (handler) {
            for(let i=0, len=handler.length; i<len; i++) {
                handler[i].apply(this, args);
            }
        }
    }
}

4. 总结 #

EventEmitter功能我们经常有使用到,尤其是在实现的一些基础类库中,用到的更多。Vue中的bus功能也是这种思想。