【面试必问】面试官:手写一个发布-订阅模型吧

135 阅读3分钟

a370e32cb34c4dc891876f6afee023fd~tplv-k3u1fbpfcp-zoom-in-crop-mark 1512 0 0 0.webp

前言:

大家在做项目时总会用到一些框架,比如无界微前端应用通信,vue全局事件总线等等。这些框架的通信原理或多或少都有用到发布订阅的思想,面试官很有可能会让你手写一个简单的发布订阅模型。

(笔者之前在介绍自己的微前端项目时就被要求手写一个发布订阅,结果现场草草写出来的代码有很多瑕疵qaq~)

介绍:

什么是发布订阅模型?

官方定义:发布订阅模型又叫观察者模型,他定义对象间的一种一对多的依赖关系,当一个对象的状态发生改变时,所有依赖它的对象都将得到通知。

说人话!!!

说人话:其实我们js应用里有很多发布订阅的例子,他们的共性就是我们可以给一些模型上绑定一些事件(回调函数),当对应的事件触发时,系统会自动执行我们的回调函数(相当于通知我们)

比如:给一个dom事件绑定点击事件


// 相当于订阅
document.body.addEventListener('click',function(){
    console.log('我被点击啦')
}) 

// 相当于发布
document.body.click() //模拟用户点击

再比如:ajax中的success事件.......

实战-手写发布订阅

在了解了发布订阅的基本概念之后,我们来尝试手写一个发布订阅模型:

首先:我们要知道发布订阅肯定是有三个要素的:

  • 发布者发布事件
  • 订阅者订阅事件
  • 订阅者取消事件

因此,我们手写发布订阅模型的主要精力应该放在实现这三个方法上

手写发布订阅模型

我们先来定义一下发布订阅模型

这个对象有三个方法:

  • listen:订阅事件
  • trigger:发布事件
  • remove:取消事件

这个对象有一个属性:

  • clientlist:用于存放订阅事件时的回调函数

基础比较好的同学可以直接阅读下面的代码,发布订阅的思想并不难,相信你可以理解绝大多数内容

如果感觉比较难理解,可以继续阅读笔者下面的解释

     var event = {
        clientList: {},
        listen: function (key, fn) {
            if (!this.clientList[key]) {
                this.clientList[key] = [];
            }
            this.clientList[key].push(fn);
        },
        trigger: function () {
            var key = Array.prototype.shift.call(arguments), // (1);
                fns = this.clientList[key];
            if (!fns || fns.length === 0) { // 如果没有绑定对应的消息
                return false;
            }
            for (var i = 0, fn; fn = fns[i++];) {
                fn.apply(this, arguments);
            }
        },
        remove: function (key, fn) {
            var fns = this.clientList[key];
            if (!fns) { // 如果key 对应的消息没有被人订阅,则直接返回
                return false;
            }
            if (!fn) { // 如果没有传入具体的回调函数,表示需要取消key 对应消息的所有订阅
                fns && (fns.length = 0);
            } else {
                for (var l = fns.length - 1; l >= 0; l--) { // 反向遍历订阅的回调函数列表
                    var _fn = fns[l];
                    if (_fn === fn) {
                        fns.splice(l, 1); // 删除订阅者的回调函数
                    }
                }
            }
        }
    };

解释发布订阅模型

基本思想:上面的代码基本思想就是,提供一个clientlist的对象结构,这个对象用于存放订阅事件的回调函数。那有同学可能会问了,这个结构为什么是一个对象不是一个数组呢? 因为发布订阅我们要区分不同的事件,所有该对象每一个key对应一个数组,数组里存放的是相同事件的回调函数。

  • listen函数:在订阅事件时需要传入订阅事件名(key),和回调函数(通知你的函数),给clientlist对象的key对应的数组push该回调函数即可

  • trigger函数:当发布事件时,执行key对应数组内的所有回调函数

  • remove函数:传入key和回调函数,从数组内删除该函数

总结

我们可以把我们的发布订阅模型和常见主流框架对比一下,我们可以发现使用是一致的,这可以证明我们总结的发布订阅原理是没有问题的。

无界eventbus通信:

image.png

vue eventbus通信:

image.png

参考资料

无界微前端官网:wujie-micro.github.io/doc/guide/c…

vue2官网:v2.cn.vuejs.org/v2/api/#vm-…