最新公告
  • 欢迎您光临起源地模板网,本站秉承服务宗旨 履行“站长”责任,销售只是起点 服务永无止境!立即加入钻石VIP
  • 手写一个基于发布订阅模式的js事件处理中心(EventEmitter)

    正文概述 掘金(是阿恒呀)   2021-07-16   607

    定义

    发布-订阅模式其实是一种对象间一对多的依赖关系,当一个对象的状态发送改变时,所有依赖于它的对象都将得到状态改变的通知。 订阅者(Subscriber)把自己想订阅的事件注册(Subscribe)到调度中心(Event Channel),当发布者(Publisher)发布该事件(Publish Event)到调度中心,也就是该事件触发时,由调度中心统一调度(Fire Event)订阅者注册到调度中心的处理代码。

    实现思路

    1. 创建一个 EventEmitter
    2. 在该类上创建一个事件中心(Map)
    3. on 方法用来把函数 fn 都加到事件中心中(订阅者注册事件到调度中心)
    4. emit 方法取到 arguments 里第一个当做 event,根据 event 值去执行对应事件中心中的函数(发布者发布事件到调度中心,调度中心处理代码)
    5. off 方法可以根据 event 值取消订阅(取消订阅)
    6. once 方法只监听一次,调用完毕后删除缓存函数(订阅一次)
    7. 注册一个 newListener 用于监听新的事件订阅

    第一步,创建一个类,并初始化一个事件存储中心

    class EventEmitter{
        // 用来存放注册的事件与回调
        constructor(){
            this._events = {};
        }
    }
    

    第二步,实现事件的订阅方法 on

    class EventEmitter{
        // 用来存放注册的事件与回调
        constructor(){
            this._events = {};
        }
    
        on(eventName, callback){
            // 由于一个事件可能注册多个回调函数,所以使用数组来存储事件队列
            const callbacks = this._events[eventName] || [];
            callbacks.push(callback);
            this._events[eventName] = callbacks
        }
    }
    

    第三步,实现事件的发布方法 emit

    class EventEmitter{
        // 用来存放注册的事件与回调
        constructor(){
            this._events = {};
        }
    
        // args 用于收集发布事件时传递的参数
        emit(eventName, ...args){
            const callbacks = this._events[eventName] || [];
            callbacks.forEach(cb => cb(...args))
        }
    }
    

    第四步,实现事件的取消订阅方法 off

    class EventEmitter{
        // 用来存放注册的事件与回调
        constructor(){
            this._events = {};
        }
        
        
        off(eventName, callback){
            const callbacks = this._events[eventName] || []
    
            const newCallbacks = callbacks.filter(fn => fn != callback && fn.initialCallback != callback /* 用于once的取消订阅 */)
    
            this._events[eventName] = newCallbacks;
        }
    }
    

    第五步,实现事件的单次订阅方法 once

    class EventEmitter{
        // 用来存放注册的事件与回调
        constructor(){
            this._events = {};
        }
        
        // 
        once(eventName, callback){
            // 由于需要在回调函数执行后,取消订阅当前事件,所以需要对传入的回调函数做一层包装,然后绑定包装后的函数
            const one = (...args)=>{
                // 执行回调函数
                callback(...args)
                // 取消订阅当前事件
                this.off(eventName, one)
            }
            // 考虑:如果当前事件在未执行,被用户取消订阅,能否取消?
    
    
    
            // 由于:我们订阅事件的时候,修改了原回调函数的引用,所以,用户触发 off 的时候不能找到对应的回调函数
            // 所以,我们需要在当前函数与用户传入的回调函数做一个绑定,我们通过自定义属性来实现
            one.initialCallback = callback;
            this.on(eventName, one)
        }
    }
    

    第六步,注册一个 newListener 用于监听新的事件订阅

    class EventEmitter{
        // 用来存放注册的事件与回调
        constructor(){
            this._events = {};
        }
    
        on(eventName, callback){
    
            // 如果绑定的事件不是newListener 就触发改回调
            if(this._events[eventName]){
                if(this.eventName !== "newListener"){
                    this.emit("newListener", eventName)
                }
            }
            // 由于一个事件可能注册多个回调函数,所以使用数组来存储事件队列
            const callbacks = this._events[eventName] || [];
            callbacks.push(callback);
            this._events[eventName] = callbacks
        }
    }
    

    测试用例

    
    const events = new EventEmitter()
    
    events.on("newListener", function(eventName){
        console.log(`eventName`, eventName)
    })
    
    events.on("hello", function(){
        console.log("hello");
    })
    
    let cb = function(){
        console.log('cb');
    }
    events.on("hello", cb)
    
    events.off("hello", cb)
    
    function once(){
        console.log("once");
    }
    events.once("hello", once)
    
    events.off("hello", once)
    events.emit("hello")
    events.emit("hello")
    
    

    完整的代码

    class EventEmitter{
        constructor(){
            this._events = {};
        }
    
        on(eventName, callback){
            if(this._events[eventName]){
                if(this.eventName !== "newListener"){
                    this.emit("newListener", eventName)
                }
            }
            const callbacks = this._events[eventName] || [];
            callbacks.push(callback);
            this._events[eventName] = callbacks
        }
    
        emit(eventName, ...args){
            const callbacks = this._events[eventName] || [];
            callbacks.forEach(cb => cb(...args))
        }
    
        once(eventName, callback){
            const one = (...args)=>{
                callback(...args)
                this.off(eventName, one)
            }
            one.initialCallback = callback;
            this.on(eventName, one)
        }
    
         off(eventName, callback){
            const callbacks = this._events[eventName] || []
            const newCallbacks = callbacks.filter(fn => fn != callback && fn.initialCallback != callback /* 用于once的取消订阅 */)
            this._events[eventName] = newCallbacks;
        }
    }
    
    

    起源地下载网 » 手写一个基于发布订阅模式的js事件处理中心(EventEmitter)

    常见问题FAQ

    免费下载或者VIP会员专享资源能否直接商用?
    本站所有资源版权均属于原作者所有,这里所提供资源均只能用于参考学习用,请勿直接商用。若由于商用引起版权纠纷,一切责任均由使用者承担。更多说明请参考 VIP介绍。
    提示下载完但解压或打开不了?
    最常见的情况是下载不完整: 可对比下载完压缩包的与网盘上的容量,若小于网盘提示的容量则是这个原因。这是浏览器下载的bug,建议用百度网盘软件或迅雷下载。若排除这种情况,可在对应资源底部留言,或 联络我们.。
    找不到素材资源介绍文章里的示例图片?
    对于PPT,KEY,Mockups,APP,网页模版等类型的素材,文章内用于介绍的图片通常并不包含在对应可供下载素材包内。这些相关商业图片需另外购买,且本站不负责(也没有办法)找到出处。 同样地一些字体文件也是这种情况,但部分素材会在素材包内有一份字体下载链接清单。
    模板不会安装或需要功能定制以及二次开发?
    请QQ联系我们

    发表评论

    还没有评论,快来抢沙发吧!

    如需帝国cms功能定制以及二次开发请联系我们

    联系作者

    请选择支付方式

    ×
    迅虎支付宝
    迅虎微信
    支付宝当面付
    余额支付
    ×
    微信扫码支付 0 元