面试官:请实现Javascript发布-订阅模式

news2024/12/23 9:24:02

简介

发布-订阅模式又叫做观察者模式,他定义了一种一对多的依赖关系,即当一个对象的状态发生改变的时候,所有依赖他的对象都会得到通知。

回忆曾经

作为一名前端开发人员,给DOM节点绑定事件可是再频繁不过的事情。比如如下代码

    document.body.addEventListener('click',function () {
        alert(2333);
    },false);
    document.body.click();//模拟点击事件

这里我们订阅了document.body的click事件,当body被点击的时候,他就向订阅者发布这个消息,弹出2333.我们也可以随意的增加和删除订阅者,当消息一发布,所有的订阅者都会收到消息。

    document.body.addEventListener('click',function () {
        alert(11111);
    },false);
    document.body.addEventListener('click',function () {
        alert(222);
    },false);
    document.body.addEventListener('click',function () {
        alert(333);
    },false);
    document.body.click();//模拟点击事件

值得注意的是,手动触发事件这里我们直接用了document.body.click();但是更好的做法是IE下用fireEvent,标准浏览器下用dispatchEvent,如下:

    let fireEvent = function (element,event) {
        if (document.createEventObject) {
            var evt = document.createEventObject();
            return element.fireEvent('on'+event,evt);
        }else{
            var evt = document.createEvent('HTMLEvents');
            evt.initEvent(event,true,true);
            return element.dispatchEvent(evt);
        }
    }
    document.addEventListener('shout',function (event) {
        alert('shout');
    })
    fireEvent(document,'shout');

畅谈现在

人的日常生活离不开各种人际交涉,比如你的朋友有很多,这时候你要结婚了,要以你为发布者,打开你的通讯录,挨个打电话通知各个订阅者你要结婚的消息。抽象一下,实现发布-订阅模式需要:

  1. 发布者(你)
  2. 缓存列表(通讯录,你的朋友们相当于订阅了你的所有消息)
  3. 发布消息的时候遍历缓存列表,依次触发里面存放的订阅者的回调函数(挨个打电话)
  4. 另外,回调函数中还可以添加很多参数,,订阅者可以接收这些参数,比如你会告诉他们婚礼时间,地点等,订阅者收到消息后可以进行各自的处理。
let yourMsg = {};
yourMsg.peopleList = [];
yourMsg.listen = function (fn) {
    this.peopleList.push(fn);
}
yourMsg.triger = function () {
    for(var i = 0,fn;fn=this.peopleList[i++];){
        fn.apply(this,arguments);
    }
}

yourMsg.listen(function (name) {
    console.log(`${name}收到了你的消息`);
})
yourMsg.listen(function (name) {
    console.log('哈哈');
})

yourMsg.triger('张三');
yourMsg.triger('李四');

参考 前端进阶面试题详细解答

  • 以上就是一个简单的发布-订阅的实现,但是我们会发现订阅者会收到发布者发布的每一条信息,如果李四比较阴暗,不想听到你结婚的消息,只想听到你的坏消息,比如你被开除了,他就心里高兴。这时候我们就需要加一个key,让订阅者只订阅自己感兴趣的消息。
let yourMsg = {};
yourMsg.peopleList ={};
yourMsg.listen = function (key,fn) {
    if (!this.peopleList[key]) { //如果没有订阅过此类消息,创建一个缓存列表
        this.peopleList[key] = [];
    }
    this.peopleList[key].push(fn);
}
yourMsg.triger = function () {
    let key = Array.prototype.shift.call(arguments);
    let fns = this.peopleList[key];
    if (!fns || fns.length == 0) {//没有订阅 则返回
        return false;
    }
    for(var i=0,fn;fn=fns[i++];){
        fn.apply(this,arguments);
    }
}

yourMsg.listen('marrgie',function (name) {
    console.log(`${name}想知道你结婚`);
})
yourMsg.listen('unemployment',function (name) {
    console.log(`${name}想知道你失业`);
})

yourMsg.triger('marrgie','张三');
yourMsg.triger('unemployment','李四');

  • 你需要发布消息,同样的所有的人都有朋友圈,也都需要发布消息,因此我们有必要把发布-订阅的功能提取出来,放在一个单独的对象内,谁需要谁去动态安装发布-订阅功能(installEvent函数实现了动态安装发布-订阅功能)。
var event = {
    peopleList:[],
    listen:function (key,fn) {
        if (!this.peopleList[key]) { //如果没有订阅过此类消息,创建一个缓存列表
        this.peopleList[key] = [];
        }
        this.peopleList[key].push(fn)
    },
    trigger:function () {
         let key = Array.prototype.shift.call(arguments);
        let fns = this.peopleList[key];
        if (!fns || fns.length == 0) {//没有订阅 则返回
            return false;
        }
        for(var i=0,fn;fn=fns[i++];){
            fn.apply(this,arguments);
        }
    }
}

var installEvent  = function (obj) {
    for(var i in event){
        obj[i] = event[i];
    }
}

let yourMsg = {};
installEvent(yourMsg);
yourMsg.listen('marrgie',function (name) {
    console.log(`${name}想知道你结婚`);
})
yourMsg.listen('unemployment',function (name) {
    console.log(`${name}想知道你失业`);
})

yourMsg.trigger('marrgie','张三');
yourMsg.trigger('unemployment','李四');
  • 有时间我们需要取消订阅的事件,比如李四是你的好朋友,但是因为一件事情,你俩闹掰了,你把他从你的通讯录中给删除掉了,这里我们给event增加一个remove方法;
remove:function (key,fn) {
      var fns = this.clientList[key];
      if(!fns){
          return false;
      }  
      if(!fn){
          fns && (fns.length=0)
      }else{
          for (let index = 0; index < fns.length; index++) {
              const _fn = fns[index];
              if(_fn === fn){
                  fns.splice(index,1);
              }
          }
      }
    }

发布-订阅的顺序探讨

我们通常所看到的都是先订阅再发布,但是必须要遵守这种顺序吗?答案是不一定的。如果发布者先发布一条消息,但是此时还没有订阅者订阅此消息,我们可以不让此消息消失于宇宙之中。就如同QQ离线消息一样,离线的消息被保存在服务器中,接收人下次登录之后,才会收到此消息。同样的,我们可以建立一个存放离线事件的堆栈,当事件发布的时候,如果此时还没有订阅者订阅这个事件,我们暂时把发布事件的动作包裹在一个函数里,这些包装函数会被存入堆栈中,等到有对象来订阅事件的时候,我们将遍历堆栈并依次执行这些包装函数,即重发里面的事件,不过离线事件的生命周期只有一次,就像qq未读消息只会提示你一次一样。

JavaScript实现发布-订阅模式的便利性

因为JavaScript有回调函数这个优势存在,我们写开发-订阅显得更简单一点。传统的发布-订阅比如Java通常会把订阅者自身当成引用传入发布者对象中,同时订阅者对象还需提供一个名为诸如update的方法,供发布者对象在合适的时候调用。下面代码用js模拟下传统的实现。

function Dep() {
    this.subs = [];
}
Dep.prototype.addSub = function (sub) {
    this.subs.push(sub);
}
Dep.prototype.notify = function () {
    this.subs.forEach(sub=>sub.update());
}
function Watcher(fn) {
    this.fn = fn;
}
Watcher.prototype.update = function () {
     this.fn();
}

var dep = new Dep();
dep.addSub(new Watcher(function () {
    console.log('okokok');
}))
dep.notify();

小结

  • 发布-订阅的优势很明显,做到了时间上的解耦和对象之间的解耦,从架构上看,MVC,MVVM都少不了发布-订阅的参与,我们常用的Vue也是基于发布-订阅的,最近会抽时间写下vue的源码实现,同样的node中的EventEmitter也是发布订阅的,之前也手写过它的实现。
  • 发布-订阅同时也是有缺点存在的,创建订阅者本身要消耗一定的时间和内存,而且当你订阅一个消息以后,可能此消息最后都未发生,但是这个订阅者会始终存在于内存中。如果程序中大量使用发布-订阅的话,也会使得程序跟踪bug变得困难。

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

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

相关文章

拉伯证券|港股站上“年线”,更有盘中暴涨160%!A股地产板块集体大涨

今天上午&#xff0c;A股首要指数涨跌互现&#xff0c;“地产链”股票团体大涨。 港股方面&#xff0c;恒生指数和恒生科技指数盘中双双站上年线&#xff0c;多头攻势微弱。 港股盘中再现异动个股&#xff0c;有港股盘中暴升约160%。 A股首要指数涨跌互现“地产链”股票团体大…

Web3中文|年终回顾:2022年加密市场六大黑天鹅

2022年对加密货币和NFT来说是一段艰难的旅程&#xff0c;随着市场情绪的普遍低迷以及加密寒冬的到来&#xff0c;Web3跌入谷底。 从rug pulls骗局到NFT价格大跌&#xff0c;再到加密交易所和对冲基金的崩溃&#xff0c;还有数十亿美元化为泡沫&#xff0c;政府部门的监管和刑事…

如何使用 LightningChart 创建 JavaScript 散点图?

LightningChart JS是一款高性能的JavaScript图标库&#xff0c;专注于实时数据可视化&#xff0c;以“快如闪电”享誉全球&#xff0c;是Microsoft Visual Studio数据展示速度最快的2D和3D图表制图组件&#xff0c;可实时呈现超过10亿数据点的海量数据。LightningChart .JS | 下…

网络舆情监测服务系统技术说明,日常网络舆情监测记录?

随着互联网快速发展&#xff0c;网络舆情监测服务系统技术可以实现对互联网信息的全面监控&#xff0c;具备敏感词监控、图片识别、预警推送、舆情浏览、综合分析、事件分析、小视频监测等功能。接下来TOOM舆情监测带您简单了解网络舆情监测服务系统技术说明&#xff0c;日常网…

云开发 | 云数据库的创建及其增删改查

前言 虽然说微信云开发开始收费了&#xff0c;不过使用起来确实方便&#xff0c;针对于数据量不大&#xff0c;轻量级的小程序应用来说&#xff0c;云开发包含云数据库、云函数、云存储这三大功能&#xff0c;使用云开发完全可以进行独立开发&#xff0c;甚至不需要依赖于后端。…

worepress 优化记录

关闭评论和头像禁用不必要的插件参加插件。背字根/WordPress小工具 - 码云 - 开源中国 (gitee.com)smalltool 文件夹上传到 /wp-content/plugins/smalltool 下使用缓存WP RocketWP火箭- 帮助提高页面加载速度并减少服务器负载。 高下载速度&#xff0c;反过来&#xff0c;降低了…

数据结构教程

数据结构教程数据结构数据结构与算法为什么要学习数据结构和算法&#xff1f;阅读本教程前&#xff0c;您需要了解的知识&#xff1f;常见的数据结构常用算法插入排序希尔排序归并排序随机机化快速排序双路快速排序三路排序算法排序算法衍生问题堆堆的基本存储堆的 shift up堆的…

DAISY : Linux 上可服务于视力缺陷者的福音

导读如果你是盲人或像我一样有视力障碍&#xff0c;你可能经常需要各种软硬件才能做到视觉正常的人们视之为理所当然的事情。这其中之一就是阅读的印刷图书的专用格式&#xff1a;布莱叶盲文&#xff08;Braille&#xff09;&#xff08;假设你知道怎样阅读它&#xff09;或特殊…

ThinkPHP 加载自定义扩展文件

很久没有用过TP了&#xff0c;自定义扩展文件怎么加载的都不太清楚了&#xff0c;重新捋一遍&#xff0c;记录一下。 ThinkPHP版本 ^6.1.0 目录 1.将自定义扩展文件放入extend 2.设置类文件命名空间 extend下直接是类文件&#xff0c;不需设置命名空间 有层级的类文件&…

回顾2022,展望2023

文章目录初来乍到为什么是CSDN期间期间文章新年愿望初来乍到 额(⊙﹏⊙)&#xff0c;要从何说起呢&#xff1f; 我看了一下&#xff0c;写这篇文章时正好是我来到CSDN这个大家庭的75天&#xff0c;对于我而言75天说长不长&#xff0c;说短也不短。75天相比于CSDN中的前辈们来…

SpringBoot+VUE前后端分离项目学习笔记 - 【10 Vue实现增删改查】

vue安装axios&#xff1a; npm i axios -S 前端代码 request.js封装 新建utils/request.js import axios from axiosconst request axios.create({baseURL: http://localhost:9090/,timeout: 5000 })// request 拦截器 // 可以自请求发送前对请求做一些处理 // 比如统一…

磷脂聚乙二醇葡萄糖 DSPE-PEG-Glucose简介;脂质体表面进行聚乙二醇修饰;DSPE-PEG-葡萄糖

中文名称 磷脂聚乙二醇葡萄糖 葡萄糖聚乙二醇磷脂 简称 DSPE-PEG-Glucose Glucose-PEG-DSPE 分子量 2000/3000/3400/40005000/10000 溶剂 溶于部分常规有机溶剂 存储条件 -20冷冻保存&#xff0c;惰性气体保护 结构式 化学…

JVM面试题详解系列——垃圾收集器详解

垃圾收集器 Serial 收集器&#xff08;GC日志标识&#xff1a;DefNew&#xff09; Serial&#xff08;串行&#xff09;收集器是最基本、历史最悠久的垃圾收集器了。大家看名字就知道这个收集器是一个单线程收集器了。 它的 “单线程” 的意义不仅仅意味着它只会使用一条垃圾…

Windows 卡顿问题整理

右键卡顿 1 开始&#xff0c;运行&#xff0c;输入regedit&#xff0c;打开注册表&#xff1b; 找到注注册表项&#xff1a;HKEY_CLASSES_ROOT/Directory/Background/shellex/ContextMenuHandlers 。 系统一般有 5 个 Key&#xff1a;留下 New&#xff0c;其他删除&#xff0c;…

统计大写字母-C语言实现

任务描述 本关任务&#xff1a;统计大写字母个数。 相关知识 视频1 初识文本处理之单词计数II — C 语言的逻辑运算符 逻辑运算符 C 语言定义了3个逻辑运算符&#xff0c;其表示方法及含义如下表所示。 逻辑运算C语言符号表示说明与&&双目运算符&#xff0c;若两个…

九 深度剖析数据在内存中的存储

目录 一.整形在内存中的存储 1.原码&#xff0c;反码&#xff0c;补码 &#xff08;1&#xff09;正数的原反补码 &#xff08;2&#xff09;负数的原反补码 2.大小端介绍 二.浮点型在内存中的存储 1.浮点型的存储 2.浮点型的读取 一.整形在内存中的存储 1.原码&#…

【学习】RL

sparse reward我们不知道行动是好是坏&#xff0c;大多数情况下&#xff0c;如果r 0&#xff0c;那怎么解决呢&#xff1f;例如&#xff0c;机器人手臂将螺栓固定在螺丝上&#xff0c;开发人员应该定义额外奖励来指导代理&#xff08;reward shaping&#xff09;。reward shap…

PyFlink使用说明:建表及连接Mysql数据库

PyFlink1.16.0 使用说明&#xff1a;建表及连接Mysql数据库引言安装运行环境PyFlink创建作业环境一、创建一个 Table API 批处理表环境二、创建一个 Table API 流处理表环境三、创建一个 DataStream API 数据流处理环境PyFlink建表一、从Python List对象创建一个 Table二、创建…

尚硅谷-SpringSecurity

一、SpringSecurity是什么 SpringSecurti基于Spring框架&#xff0c;提供了一套Web应用安全性的完整解决方案 一般来说&#xff0c;Web应用的安全性包括用户认证&#xff08;Authenticataion&#xff09;和用户授权&#xff08;Authorization&#xff09;两个部分 这两点也是S…

【2023程序员必看】产品经理行业分析

今天我要给大家推荐一个政策好&#xff0c;薪酬高&#xff0c;发展好&#xff0c;门槛低&#xff0c;且容易实现CEO总裁梦的岗位。当当当当当~产品经理&#xff0c;不用写代码的高薪互联网岗位&#xff01; 接下来我们就来聊聊它的政策机会&#xff0c;职业前景与薪资待遇&…