当前位置: 首页 > 后端技术 > Node.js

clipboard.js代码分析(二)-emitter

时间:2023-04-03 15:46:44 Node.js

上一篇介绍了clipboard.js的工具库中的第一个依赖select。该工具库主要完成将任意DOM元素复制到剪贴板的功能。本次介绍clipboard.js源码中的第二个依赖的光照工具库tiny-emitter。本工具库主要用于实现一个简单的基于监听发布者模式的事件分发和接收。代码在我的es6中改写后只有40行,不依赖第三方库,实现的功能比较强大,可以根据实际情况轻松扩展。快速上手在研究源码之前,我们先来看看最常见的使用场景。constEmitter=require('./emitter')letemitter=newEmitter()//在一个事件上letsayHello=name=>console.log(`hello,${name}`)emitter.on('helloName',sayHello)//在同一个事件上发出一个事件//emitter.emit('helloName','donngzhe')//letobj={prefix:'smith',thankName(name){console.log(`hello,${this.prefix}.${name}`)return`hello,${this.prefix}.${name}`}}emitter.on('helloName',obj.thankName,obj)emitter.emit('helloName','dongzhe')//newotheremitter可以在这里分组不同组可以有相同的eventNameletemitter1=newEmitter()letsayHaHa=name=>console.log(`haha,${name}`)emitter1.on('helloName',sayHaHa)//emit一个事件emitter1.emit('helloName','东哲')可以看出每个事件管理器都是一个对象,可以根据不同的业务使用场景模块创建不同的事件管理器。事件管理器最基本的功能就是动态订阅和派发事件,当然还有取消事件。用于同一个主模块下的不同子模块和不同主模块之间的通信,支持动态绑定作用域。如果你用过Vue的父子组件事件通信和eventBus,你应该对事件管理器不陌生。源码实现事件管理模型主要由4个函数组成,on用于订阅事件,一个事件订阅多个触发函数emit用于发布事件,发布时会触发一次事件订阅函数,只订阅事件triggeronceoffcancel订阅事件,支持指定取消、批量取消和全部取消代码结构classE{constructor(){this.eventObj={}}on(){}once(){}emit(){}off(){}}module.exports=EEmitter对象中有一个事件对象,以键值对的形式存储了事件名称和对应的触发事件。订阅事件onSubscribeingevents就是把要触发的函数放到事件对应的对象中。如果事件不存在,则需要对其进行初始化。一个事件可以动态订阅多个触发函数。并且支持指定作用域,可以远程调用任意模块的函数。on(eventName,callback,ctx){//一个eventName可以绑定多个事件(this.eventObj[eventName]||(this.eventObj[eventName]=[])).push({callback,ctx})returnThis}publisheventemit是相对于subscribeevent,releaseevent,releaseevent接收事件名和触发函数参数,只需要依次执行事件订阅对应的触发函数即可,参数可以使用es6restoperator。emit(eventName,...args){让eventArr=(this.eventObj[eventName]||[]).slice()eventArr.forEach(ele=>ele.callback.call(ele.ctx,args))返回这个}取消事件off和订阅的事件相比,应该也可以取消事件。有许多取消事件的选项。可以指定一个或多个触发函数取消事件订阅,也可以直接取消整个事件。取消事件接收被取消的事件名称,以及一个可选的函数对象或函数对象数组(我自己添加的)。如果传入指定的触发函数对象,则通过遍历所有触发函数过滤掉需要取消的触发函数,最后重新赋值。如果没有传递触发函数,则认为取消整个订阅事件,可以直接从全局事件对象中删除订阅对象。off(eventName,callback){if(Object.prototype.toString.call(callback)==="[objectArray]"){callback.forEach(func=>this.off(eventName,func))返回这个}让liveEvents=[]letobj=this.eventObjleteventArr=obj[eventName]//如果没有回调则删除整个eventName对象if(eventArr&&callback){liveEvents=eventArr.filter(ele=>(ele.callback!==回调&&ele.callback._!==回调))}(liveEvents.length)?obj[eventName]=liveEvents:deleteobj[eventName]returnthis}最重要的是下面这行代码,使用filter过滤掉需要取消的触发函数,ele.callback._!==callback是为了兼容性曾经说过。liveEvents=eventArr.filter(ele=>(ele.callback!==callback&&ele.callback._!==callback))触发一次有时候我们只需要触发一个订阅事件,比如用户刚刚登录获取触发一次后不需要历史消息或通知消息,所以有了once函数,once函数的主要工作原理是在函数内部添加一个代理函数listener代理函数,作为触发函数的代理。就是增加逻辑。这个逻辑是在第一次执行trigger函数的时候自动执行off函数,取消trigger函数的逻辑。letlistener=(...args)=>{this.off(eventName,listener)callback.apply(ctx,args)}//因为监听器在回调上封装了一层,所以需要指定一个规则,可以找到回调listener._=callback因为listener在callback上封装了一层proxy,所以需要指定一个可以找到callback的规则,这样off函数传给cancel函数的时候,我们就可以找到了以兼容的方式。最后,代理函数listeneronce(eventName,callback,ctx){letlistener=(...args)=>{this.off(eventName,listener)callback.apply(ctx,args)}//因为listener它在callback上封装了一层,所以需要指定一条规则,可以找到callbacklistener._=callbackreturnthis.on(eventName,listener,ctx)}完整代码是在原代码的基础上重写的es6,并添加了一些逻辑,可以和原来的代码对比一下,最终完整代码如下可以绑定多个event(this.eventObj[eventName]||(this.eventObj[eventName]=[])).push({callback,ctx})returnthis}once(eventName,callback,ctx){letlistener=(...args)=>{this.off(eventName,listener)callback.apply(ctx,args)}//因为listener在callback上封装了一层,所以需要指定一个可以找到callbacklistener的规则._=回调返回这个.on(eventName,listener,ctx)}emit(eventName,...args){leteventArr=(this.eventObj[事件名称]||[]).slice()eventArr.forEach(ele=>ele.callback.call(ele.ctx,args))返回这个}off(eventName,callback){if(Object.prototype.toString.call(callback)==="[objectArray]"){callback.forEach(func=>this.off(eventName,func))returnthis}letliveEvents=[]letobj=this.eventObjleteventArr=obj[eventName]//如果没有回调,删除整个eventName对象长度)?obj[eventName]=liveEvents:deleteobj[eventName]returnthis}}module.exports=E结论这只是一个比较简单的事件订阅发布者,但是核心思想比较完整,采用面向对象,订阅发布者模式,proxymode等,并且可以根据自己的需求方便的进行扩展,比如我扩展的批量取消,还可以添加批量订阅,甚至可以使用promises来封装异步触发器,每个函数返回对象本身就可以完成上链调用,比如订阅完成后立即触发初始化完成等。