最新公告
  • 欢迎您光临起源地模板网,本站秉承服务宗旨 履行“站长”责任,销售只是起点 服务永无止境!立即加入钻石VIP
  • 前端JS高频面试题---1.发布-订阅模式

    正文概述 掘金(Nicnic)   2021-03-29   526

    一、概念

    1.定义

    发布-订阅模式其实是一种对象间一对多的依赖关系,当一个对象的状态发送改变时,所有依赖于它的对象都将得到状态改变的通知。

    订阅者(Subscriber)把自己想订阅的事件注册(Subscribe)到调度中心(Event Channel),当发布者(Publisher)发布该事件(Publish Event)到调度中心,也就是该事件触发时,由调度中心统一调度(Fire Event)订阅者注册到调度中心的处理代码。

    二、实现

    1.实现思路

    创建一个对象

    • 在该对象上创建一个缓存列表(调度中心Event Channel)
    • on方法用来把函数添加到缓存列表中(订阅者注册事件到调度中心)
    • emit方法取到argument里第一个当作event,根据event值去执行对应缓存列表中的函数(发布者发布事件到调度中心,调度中心处理代码)
    • off方法可以根据event值取消订阅(取消订阅)
    • once方法只监听一次,调用完毕后删除缓存函数(订阅一次)

    2.简单demo

    class EventEmitter {
        constructor() {
            // 缓存列表
            this.listener = {}
        }
    
        // 订阅
        on(eventName, fn) {
            // 如果对象中没有对应的 event 值,也就是说明没有订阅过,就给 event 创建个缓存列表
            // 如有对象中有相应的 event 值,把 fn 添加到对应 event 的缓存列表里
            if(!this.listener[eventName]){
                this.listener[eventName] = [];
            }
            this.listener[eventName].push(fn);
        }
    
        // 取消订阅
        off(eventName, fn) {
            let callbacks = this.listener[eventName];
            // 缓存列表中没有对应的fn,返回false
            if(!callbacks){
                return false;
            }
            if(!fn){
                // 如果未传入fn,则将缓存列表中对应的fn都清空
                callbacks && (callbacks.length = 0);
            } else {
                let cb;
                // 遍历所对应的fn,判断和那个fn相同,相同则删除
                for (let i = 0, cbLen = callbacks.length; i < cbLen; i++) {
                    cb = callbacks[i];
                    if (cb == fn || cb.fn == fn) {
                        callbacks.splice(i, 1);
                        break
                    }
                }
            }
        }
    
        // 监听一次
        once(eventName, fn) {
            // 先绑定,运行时删除对应的值
            let on = () => {
                this.off(eventName, on);
                fn.apply(this, arguments);
            }
    
            on.fn = fn;
            this.on(eventName, on);
    
        }
    
        // 发布
        emit(eventName, data) {
            const callbacks = this.listener[eventName];
            if(callbacks) {
                callbacks.forEach((c) => {
                    c(data);
                })
            }
        }
    }
    
    let a = new EventEmitter();
    function aa(x) {
        console.log(x);
    }
    a.on("kak", aa)
    a.on("kak", (data) => {
        console.log("1", data);
    })
    
    
    a.emit('kak', 'hahahah');
    a.off('kak',aa);
    a.emit('kak', 'hahahah');
    

    3.vue中的event Bus

    function eventsMixin (Vue) {
        var hookRE = /^hook:/;
        Vue.prototype.$on = function (event, fn) {
            var this$1 = this;
    
        var vm = this;
        // event 为数组时,循环执行 $on
        if (Array.isArray(event)) {
            for (var i = 0, l = event.length; i < l; i++) {
                this$1.$on(event[i], fn);
            }
        } else {
            (vm._events[event] || (vm._events[event] = [])).push(fn);
            // optimize hook:event cost by using a boolean flag marked at registration 
            // instead of a hash lookup
            if (hookRE.test(event)) {
                vm._hasHookEvent = true;
            }
        }
        return vm
    };
    
    Vue.prototype.$once = function (event, fn) {
        var vm = this;
        // 先绑定,后删除
        function on () {
            vm.$off(event, on);
            fn.apply(vm, arguments);
        }
        on.fn = fn;
        vm.$on(event, on);
        return vm
    };
    
    Vue.prototype.$off = function (event, fn) {
        var this$1 = this;
    
        var vm = this;
        // all,若没有传参数,清空所有订阅
        if (!arguments.length) {
            vm._events = Object.create(null);
            return vm
        }
        // array of events,events 为数组时,循环执行 $off
        if (Array.isArray(event)) {
            for (var i = 0, l = event.length; i < l; i++) {
                this$1.$off(event[i], fn);
            }
            return vm
        }
        // specific event
        var cbs = vm._events[event];
        if (!cbs) {
            // 没有 cbs 直接 return this
            return vm
        }
        if (!fn) {
            // 若没有 handler,清空 event 对应的缓存列表
            vm._events[event] = null;
            return vm
        }
        if (fn) {
            // specific handler,删除相应的 handler
            var cb;
            var i$1 = cbs.length;
            while (i$1--) {
                cb = cbs[i$1];
                if (cb === fn || cb.fn === fn) {
                    cbs.splice(i$1, 1);
                    break
                }
            }
        }
        return vm
    };
    
    Vue.prototype.$emit = function (event) {
        var vm = this;
        {
            // 传入的 event 区分大小写,若不一致,有提示
            var lowerCaseEvent = event.toLowerCase();
            if (lowerCaseEvent !== event && vm._events[lowerCaseEvent]) {
                tip(
                    "Event \"" + lowerCaseEvent + "\" is emitted in component " +
                    (formatComponentName(vm)) + " but the handler is registered for \"" + event + "\". " +
                    "Note that HTML attributes are case-insensitive and you cannot use " +
                    "v-on to listen to camelCase events when using in-DOM templates. " +
                    "You should probably use \"" + (hyphenate(event)) + "\" instead of \"" + event + "\"."
                );
            }
        }
        var cbs = vm._events[event];
        if (cbs) {
            cbs = cbs.length > 1 ? toArray(cbs) : cbs;
            // 只取回调函数,不取 event
            var args = toArray(arguments, 1);
            for (var i = 0, l = cbs.length; i < l; i++) {
                try {
                    cbs[i].apply(vm, args);
                } catch (e) {
                    handleError(e, vm, ("event handler for \"" + event + "\""));
                }
            }
        }
        return vm
    };
    }
    
    /***
       * Convert an Array-like object to a real Array.
       */
    function toArray (list, start) {
        start = start || 0;
        var i = list.length - start;
        var ret = new Array(i);
        while (i--) {
              ret[i] = list[i + start];
        }
        return ret
    } 
    

    三、 总结

    1. 优点
      • 对象之间解耦
      • 异步编程中,可以更松耦合的代码编写
    2. 缺点
      • 创建订阅者本身要消耗一定的时间和内存
      • 虽然可以弱化对象之间的联系,多个发布者和订阅者嵌套一起的时候,程序难以跟踪维护

    四、 扩展(发布-订阅模式与观察者模式的区别)

    很多地方都说发布-订阅模式是观察者模式的别名,但是他们真的一样吗?是不一样的。

    直接上图:

    前端JS高频面试题---1.发布-订阅模式

    观察者模式:观察者(Observer)直接订阅(Subscribe)主题(Subject),而当主题被激活的时候,会触发(Fire Event)观察者里的事件。

    发布订阅模式:订阅者(Subscriber)把自己想订阅的事件注册(Subscribe)到调度中心(Event Channel),当发布者(Publisher)发布该事件(Publish Event)到调度中心,也就是该事件触发时,由调度中心统一调度(Fire Event)订阅者注册到调度中心的处理代码。

    差异

    • 在观察者模式中,观察者是知道 Subject 的,Subject 一直保持对观察者进行记录。然而,在发布订阅模式中,发布者和订阅者不知道对方的存在。它们只有通过消息代理进行通信。
    • 在发布订阅模式中,组件是松散耦合的,正好和观察者模式相反。
    • 观察者模式大多数时候是同步的,比如当事件触发,Subject 就会去调用观察者的方法。而发布-订阅模式大多数时候是异步的(使用消息队列)。
    • 观察者模式需要在单个应用程序地址空间中实现,而发布-订阅更像交叉应用模式。

    起源地下载网 » 前端JS高频面试题---1.发布-订阅模式

    常见问题FAQ

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

    发表评论

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

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

    联系作者

    请选择支付方式

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