实现一个全局事件总线并发布到NPM

news2024/11/16 23:53:12

前言

Vue2开发过程中,会碰到非父子组件情况,我们大多数会使用Vue提供的自定义实例来解决这个问题,但在Vue3之后就移除了$on/$off/$once/emit 相关API,不再提供自定义实例,而是推荐使用一些第三方库如mitt、tiny-emitter来实现这件事情,接下来就这两个库进行阅读并实现一个自己的事件总线

关于发布订阅

1.发布订阅是一种一对多的对象关系,当一个对象状态改变时候,所有依赖于它的对象都会得到通知
2.订阅者把订阅事件注册到调度中心,发布者去发布事件时,会触发调度中心对应的订阅者订阅的该事件

mitt

mitt函数返回一个对象, 将mitt核心源码转JS后,一一拆解理解

function mitt(all) {
all = all || new Map();
return {all,on(type, handler) {//查找 map中是否有这个keyconst handlers = all.get(type);//存在的话 就给这个key 增加事件if (handlers) {handlers.push(handler);} else {//不存在就创建 value是一个数组all.set(type, [handler]);}},off(type, handler) {//取消订阅const handlers = all.get(type);if (handlers) {// handlers 对应的就是事件的数组if (handler) {//无符号位移运算符//把 32 位数字中的所有有效位整体右移,再使用符号位的值填充空位。移动过程中超出的值将被丢弃//对于负数来说,无符号右移将使用 0 来填充所有的空位,同时会把负数作为正数来处理,所得结果会非常大所以//如果找不到的话 -1 >>> 0 返回的结果是4294967295 就相当于无效了 , 这个操作符 省去了 判断-1的操作。。handlers.splice(handlers.indexOf(handler) >>> 0, 1);} else {//如果没传type 则直接清空 事件all.set(type, []);}}},emit(type, evt) {let handlers = all.get(type);if (handlers) {handlers.slice().map((handler) => {handler(evt);});}//不管是什么事件的触发 都会顺带触发 *的订阅事件handlers = all.get("*");if (handlers) {handlers.slice().map((handler) => {handler(type, evt);});}},
};
} 

1.mitt函数返回一个对象,其中all属性 为一个Map,用于存储Key Value形式的对象,方便我们存储订阅事件
2.需要注意的是 无符号位移运算符的理解,不然还真没法看懂, 就是一个获取索引的写法。
3.on 订阅, emit发布, off移除订阅

使用

const emitter = mitt();

function onFoo() {
	console.log('Harexs')
}
emitter.on("foo", onFoo);


emitter.off("foo", onFoo);

console.log(emitter); 

tiny-emitter

tiny-emitter 的实现与mitt返回对象不同,它通过原型挂载的形式,所有创建的对象共享原型上的方法使用

let mitt = new E();
console.dir(Object.getPrototypeOf(mitt)); 
function E() {
	// Keep this empty so it's easier to inherit from
	// (via https://github.com/lipsmack from https://github.com/scottcorgan/tiny-emitter/issues/3)
}

E.prototype = {
	on: function (name, callback, ctx) {
		//创建变量e 指向this的属性 不存在则创建
		var e = this.e || (this.e = {});
		//返回对应name的事件列表不存在则为空数组
		//push一个对象,包含事件回调以及this指向
		(e[name] || (e[name] = [])).push({
			fn: callback,
			ctx: ctx,
		});
		//最后返回this对象
		return this;
	},

	once: function (name, callback, ctx) {
		var self = this;

		//实现once 的大前提是我们需要传入命名函数,存在引用关系可以让我们移除
		function listener() {
			//函数被执行时移除 自身
			self.off(name, listener);
			// 并调用一次 once传入的callback
			callback.apply(ctx, arguments);
		}
		//用于off移除时进行对比
		listener._ = callback;
		//return on会返回this并绑定name对应的linstener事件
		return this.on(name, listener, ctx);
	},

	emit: function (name) {
		//[]是字面量形式,这里实际是 Array.slice.call(arguments,1)
		//data 截取 name参数之后 剩下的参数 返回一个新数组
		//这里是取 name以外的剩余参数
		var data = [].slice.call(arguments, 1);
		//取this下的e对象下 name属性的值 不存在的情况返回一个空数组
		var evtArr = ((this.e || (this.e = {}))[name] || []).slice();
		var i = 0;
		var len = evtArr.length;
		//如果对应name的 事件数组是空的那么这个for 也不会执行
		for (i; i < len; i++) {
			//依次将每个值(对象) 的fn属性的事件 通过apply调用 并传入 this指向 以及data剩余参数
			evtArr[i].fn.apply(evtArr[i].ctx, data);
		}
		//return this
		return this;
	},

	off: function (name, callback) {
		//同上 取e 不存在就创建
		var e = this.e || (this.e = {});
		//找到对应的订阅者
		var evts = e[name];
		var liveEvents = [];
		//当订阅 以及 要取消的callback都存在时
		if (evts && callback) {
			for (var i = 0, len = evts.length; i < len; i++) {
				//遍历数组对象 fn与callback不相等 并且 fn的_属性不等于callback
				//这个_ 属性可以在once中找到 ,它就是为了对比是否相等
				//想要全等的前提是它们传入的是 命名函数
				//匿名函数本质来说也是一个对象,每个创建的对象之间判断必定是false的
				//从堆上来说 它们的堆地址是不一样,而命名函数 由于具备栈的指向,所以才具有相等的条件
				if (evts[i].fn !== callback && evts[i].fn._ !== callback)
					//如果不相等则将数组中的这个对象 push进liveEvents//其实就是不符合移除条件的对象 push进一个新数组 到时候重新赋值给回e[name]
					liveEvents.push(evts[i]);
			}
		}

		// Remove event from queue to prevent memory leak
		// Suggested by https://github.com/lazd
		// Ref: https://github.com/scottcorgan/tiny-emitter/commit/c6ebfaa9bc973b33d110a84a307742b7cf94c953#commitcomment-5024910

		//如果liveEvents 存在内容则 给 e[name]重新赋值, 否则直接移除 这个name属性
		liveEvents.length ? (e[name] = liveEvents) : delete e[name];

		return this;
	},
}; 

它的使用 和mitt大同小异,但多出一个once的API, 并且支持我们 绑定this指向

var e1 = new E();

e1.once(
	"lff",() => {console.log("lff", this);
});
e1.emit("lff"); 

实现

结合实现

结合mitttiny-emitter的特点,接下来动手实现一个自己的版本

 const Harexs_Mitt = (all = new Map()) => {return {all,//支持this指向 - tiny-mitteron(type, fn, thisArg) {const handlers = all.get(type);const newFn = fn.bind(thisArg); //支持thisnewFn._ = fn; //用于删除时函数对比if (handlers) {//通过bind 支持this指向handlers.push(newFn);} else {all.set(type, [newFn]);}},emit(type, ...evt) {const handlers = all.get(type);if (handlers) {//由于可能会碰到once splice移除的操作 导致索引变化问题 不能正确触发函数//所以使用 slice 创建一个副本来 执行handlers.slice().forEach((handler) => handler(...evt));}//默认会触发 * 的事件 - mittconst everys = all.get("*");if (everys) {everys.slice().forEach((every) => every(...evt));}},//支持once事件 - tiny-mitteronce(type, fn, thisArg = window) {let handlers = all.get(type);function onceFn() {//函数执行时 重新获取一次 handlershandlers = all.get(type);fn.apply(thisArg, arguments);//一旦执行后 就移除本次订阅的事件handlers.splice(handlers.indexOf(onceFn) >>> 0, 1);}//用于移除时对比onceFn._ = fn;if (handlers) {handlers.push(onceFn);} else {all.set(type, [onceFn]);}},off(type, fn) {let handlers = all.get(type);let newFnAry = [];if (handlers) {if (fn) {handlers.forEach((handler) => {if (handler._ !== fn) {newFnAry.push(handler);}});//如果有匹配到的函数 则使用接收了这些函数的newFnAry赋值newFnAry.length? (handlers = newFnAry.slice()): all.set(type, []);} else {//重置typeall.set(type, []);}}},};}; 
const emit = Harexs_Mitt();
function removeFn() {
	console.log(arguments);
}

emit.once(
	"lff",
	function (e) {
		console.log(this, e);
	},
	{ name: "harexs" }
);
emit.on("lff", (e) => console.log(e));

emit.off("lff", (e) => console.log(e));

emit.on("lff", removeFn);

emit.off("lff", removeFn);

console.log(emit); 

Vue中使用

// utils.js
import emitter from 'harexs-emitter'

export const emitFire = emitter() 
//brother1.vue
import {emitFire} from 'your file url/utils.js'

emitFire.on('harexs',()=>console.log('harexs')) 
//brother2.vue
import {emitFire} from 'your file url/utils.js'

emitFire.emit('harexs') 

发布

算是我第一个尝试发布的npm包, 总结下相关的知识

1.npm login登录 ,npm publish 进行发布, 使用nrm 管理npm源
2.尝试使用microbundle 打包源文件
3.尝试编写index.d.ts 提供类型声明

使用

npm i harexs-emitter

Github: harexs-emitter

感想

第一次尝试自己编写一个小工具以及发包,有些小兴奋,无符号右位移运算符, 还没读源码开始之前 对于相关的一些知识可以说一概不知。 并且下次可以尝试使用rollup进行打包, 后续还可以增加单元测试以及文档

最后

为大家准备了一个前端资料包。包含54本,2.57G的前端相关电子书,《前端面试宝典(附答案和解析)》,难点、重点知识视频教程(全套)。



有需要的小伙伴,可以点击下方卡片领取,无偿分享

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/12031.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

第3关:Client连接及状态

ZooKeeper状态以及状态的转换 一个会话从NOT_CONNECTED状态开始&#xff0c;当客户端初始化后转换成CONNECTTING状态&#xff08;箭头1&#xff09;。 当客服端与服务器断开连接&#xff0c;状态转换成CONNECTED状态&#xff08;箭头2&#xff09;。 当客服端与服务器断开连接…

基于xsh的vbs脚本的使用(语法)

一. chr(number)含义 chr(number) 对应列表链接&#xff1a;chr码值对应列表大全_conger3400的博客-CSDN博客 常用举例&#xff1a; chr(3)&#xff1a;CtrlC/退出等待某个执行命令 chr(4)&#xff1a;CtrlD/退出会话 chr(8)&#xff1a;del回退删除一个字符 chr(9)&#xff…

Linux网络管理

文章目录 前言 网卡的存储位置&#xff08;查看网卡&#xff09; NetworkManager服务 查看网络连接状态 配置网卡参数 先备份网卡配置文件&#xff0c;再修改 查看本机的自动获取的IP 编辑网卡配置文件 重启网络服务并查看IP 另一台机器同样的方法进行网络配置&#x…

网络工程师备考1-2章(续)

一:差错控制 (1)奇偶检验 什么意思呢? 如果我们用 奇校验,就是保证传输过来的数据中的1是奇数,如果不是奇数那么说明传输错误。 (所以会增加一位,保证正确的数据的总的1一定是奇数) (2)海明码 什么是海明距离? 两个码字,例如 0 1 0 0 和 0 0 1 0 可以看到这…

人机界面在石油钻井工程中的应用:如何搭建钻井工程参数监测系统?

一、应用背景 石油钻井工程是石油开采过程中最为关键的一个环节&#xff0c;直接决定着石油开采的质量和经济效益&#xff0c;而钻井工程参数的实时监测、分析处理和存储是保证安全、可靠、高效钻井的重要途径。 随着科学技术的飞速进步&#xff0c;尤其是自动化技术的发展&a…

HTML网页设计制作大作业(div+css)---浩瀚天文 (13页有二级菜单)

⛵ 源码获取 文末联系 ✈ Web前端开发技术 描述 网页设计题材&#xff0c;DIVCSS 布局制作,HTMLCSS网页设计期末课程大作业 | | HTML期末大学生网页设计作业&#xff0c;Web大学生网页 HTML&#xff1a;结构 CSS&#xff1a;样式 在操作方面上运用了html5和css3&#xff0c; 采…

MySQL索引及调优回顾

MySQL索引及调优回顾 1.1 &#xff1a;索引诞生的背景是怎样的&#xff1f; 假如数据库表中只有10条记录&#xff0c;我们可以一条条的进行查询。假如有500万条记录呢&#xff0c;从假如还是一条条去查询可能需要的时间就会比较长&#xff0c;此时索引就诞生了。1.2 &#xf…

SSH Keylogger密码抓取

简介 SSH Keylogger终端切换用户记录用户输入的终端信息可获取密码 主要利用strace系统调试工具获取ssh的读写连接的数据&#xff0c;以达到抓取管理员登陆其他机器的明文密码的作用。 Strace strace命令是一个集诊断、调试、统计于一体的工具,常用来跟踪进程执行时的系统调…

flutter 中最详细的继承,多态,接口讲解

flutter 中最详细的继承&#xff0c;多态&#xff0c;接口讲解前言一、继承&#xff08;Extends&#xff09;二、混合 mixins&#xff08;with&#xff09;2.1、最简单的mixin2.2、on 关键字&#xff0c;基于某个类型的mixin2.3、多个mixin2.4、mixin 怎么实现多继承三、接口的…

常见简单的排序算法汇总

作者&#xff1a;~小明学编程 文章专栏&#xff1a;Java数据结构 格言&#xff1a;目之所及皆为回忆&#xff0c;心之所想皆为过往 目录 插入排序 原理 代码实现 算法性能分析 希尔排序 引入 原理 代码 算法分析 选择排序 原理 代码 堆排序 原理 代码 算法分析…

pythonUI自动化测试selenium安装使用

pythonUI自动化测试selenium安装使用一、selenium二、安装1. selenium其实虽然称之为工具&#xff0c;但是实际是python中一个库2.安装浏览器3.下载浏览器驱动三、元素定位1.常见的元素定位方式2.打开浏览器3.设置最大行4.设置隐式等待5.打开网页6.点击 登录 按钮7.设置等待8.用…

【javaEE】网络编程套接字

To u&me: 努力经营当下&#xff0c;直至未来明朗 文章目录前言一、网络编程&#xff08;没时间可以跳过&#xff09;一&#xff09;网络编程了解二&#xff09;相关基本概念二、Socket套接字三、数据报套接字通信&#xff08;UDP&#xff09;写一个最简单的UDP版本的客户端…

[附源码]java毕业设计科院垃圾分类系统

项目运行 环境配置&#xff1a; Jdk1.8 Tomcat7.0 Mysql HBuilderX&#xff08;Webstorm也行&#xff09; Eclispe&#xff08;IntelliJ IDEA,Eclispe,MyEclispe,Sts都支持&#xff09;。 项目技术&#xff1a; SSM mybatis Maven Vue 等等组成&#xff0c;B/S模式 M…

高级UI-Canvas(变换技巧,状态保存)

前言 在前面我们把Paint关于UI颜色样式的处理进行了学习&#xff0c; 其实真正高级部分就是三个点&#xff0c;渲染&#xff0c;滤镜&#xff0c;图形组合&#xff0c;而我们图形绘制比较重要的另一个对象Canvas也是需要我们去重点掌握的&#xff0c;那么这次课咱们来进行Canv…

数组与指针实验

指针与数组实验 先简单看一下以下c代码 #include <stdio.h> #include <stdlib.h> int main() {char array[10];array[0] 0x56;array[1] 0x78;array[9] 0x12;char *p (char *)malloc(10);p[0] 0x34;p[1] 0x12;printf("%p\n%p\n%p\n%p\n", array, …

[附源码]Python计算机毕业设计 家乡旅游文化推广网站

项目运行 环境配置&#xff1a; Pychram社区版 python3.7.7 Mysql5.7 HBuilderXlist pipNavicat11Djangonodejs。 项目技术&#xff1a; django python Vue 等等组成&#xff0c;B/S模式 pychram管理等等。 环境需要 1.运行环境&#xff1a;最好是python3.7.7&#xff0c;…

【微信小程序】.js文件的代码结构与Page页面的生命周期

&#x1f3c6;今日学习目标&#xff1a;第十期——.js文件的代码结构与page页面的生命周期 &#x1f603;创作者&#xff1a;颜颜yan_ ✨个人主页&#xff1a;颜颜yan_的个人主页 ⏰预计时间&#xff1a;25分钟 &#x1f389;专栏系列&#xff1a;我的第一个微信小程序 文章目录…

尚医通 (二十二) --------- MongoDB 简介

目录一、NoSQL 简介二、什么是 MongoDB ?三、MongoDB 特点四、安装 MongoDB1. 数据库2. 文档3. 集合4. 适用场景五、MongoDB 概念解析一、NoSQL 简介 NoSQL (NoSQL Not Only SQL)&#xff0c;意即反 SQL 运动&#xff0c;指的是非关系型的数据库&#xff0c;是一项全新的数据…

使用SRM系统有哪些供应商管理优势?

SRM系统就是我们常说的供应商关系管理&#xff0c;它主要是用来改善与供应链上游供应商的关系&#xff0c;改善企业与供应商的关系的新型管理机制&#xff0c;使双方关系更加紧密&#xff0c;从而实现供应双赢。相信对SRM供应商关系管理系统有了解的朋友们并不陌生&#xff0c;…

[力扣] 剑指 Offer 第二天 - 复杂链表的复制

这里写目录标题题目来源题目描述示例示例 1示例 2示例 3示例 4题目解析算法 1代码实现执行结果复杂度分析算法 2代码实现执行结果复杂度分析总结耐心和持久胜过激烈和狂热。 题目来源 来源&#xff1a;力扣&#xff08;LeetCode&#xff09; 链接&#xff1a;https://leetcode…