JavaScript发布—订阅模式

news2024/10/6 22:23:10

JavaScript发布—订阅模式

  • 1 什么是发布—订阅模式
  • 2 DOM 事件
  • 3 实现一个发布—订阅模式
  • 4 发布—订阅模式的通用实现
  • 5 取消订阅的事件
  • 6 全局的发布—订阅对象
  • 7 模块间通信

1 什么是发布—订阅模式

发布—订阅模式又叫观察者模式,它定义对象间的一种一对多的依赖关系,当一个对象的状态发生改变时,所有依赖于它的对象都将得到通知。在JavaScript开发中,我们一般用事件模型来替代传统的发布—订阅模式。

现实中有许多发布—订阅模式的例子,比如微信公众号,如果关注的公众号有新的动态会立马推送给订阅者,比如演唱会抢票等。

发布—订阅模式有以下优点:

  • 可以广泛应用于异步编程中,这是一种替代传递回调函数的方案。比如,我们可以订阅ajax请求的errorsuccess等事件,我们无需过多关注对象在异步运行期间的内部状态,只需要订阅感兴趣的事件发生点就可以。
  • 可以取代对象之间硬编码的通知机制,一个对象不用再显式地调用另外一个对象的某个接口。发布—订阅模式让两个对象松耦合地联系在一起,虽然不太清楚彼此的细节,但这不影响它们之间相互通信。当有新的订阅者出现时,发布者的代码不需要任何修改;同样发布者需要改变时,也不会影响到之前的订阅者。只要之前约定的事件名没有变化,就可以自由地改变它们。

2 DOM 事件

DOM节点上面绑定事件函数,也是发布—订阅模式的体现:

document.body.addEventListener( "click", function () {
  alert(2);
});
document.body.click(); // 模拟用户点击

在这里需要监控用户点击document.body的动作,但是我们没办法预知用户将在什么时候点击,所以我们订阅document.body上的click事件,当body节点被点击时,body节点便会向订阅者发布这个消息。

3 实现一个发布—订阅模式

我们按照以下思路实现一个发布—订阅模式:

  • 指定谁充当发布者(比如公众号运营者)
  • 给发布者添加一个缓存列表,用于存放回调函数以便通知订阅者(关注的列表)
  • 发布消息的时候,发布者会遍历缓存列表依次触发里面的回调函数(给每个关注的人发通知消息),我们还可以往回调函数里填入一些信息,订阅者接收到这些信息之后可以进行各自的处理

实现代码如下:

var officialAccount = {}; // 定义公众号拥有者

officialAccount.clientList = []; // 缓存列表

// 添加订阅者
officialAccount.listen = function (fun) {
  this.clientList.push(fun);
};

// 发布消息
officialAccount.trigger = function () {
  for (let i = 0, fun; (fun = this.clientList[i++]); ) {
    fun.apply(this, arguments);
  }
};

测试一下:

// 小明订阅消息
officialAccount.listen(function getPrice(type, price) {
  console.log("水果=", type, "价格=", price);
});

// 小红订阅消息
officialAccount.listen(function getPrice(type, price) {
  console.log("水果=", type, "价格=", price);
});

officialAccount.trigger("苹果", 200);
officialAccount.trigger("橘子", 300);

// ------以下为输出-----------
// 水果= 苹果 价格= 200
// 水果= 苹果 价格= 200
// 水果= 橘子 价格= 300
// 水果= 橘子 价格= 300

我们已经实现了一个最简单的发布—订阅模式,但这里还存在一些问题。订阅者接收到了发布者发布的每个消息,比如说小明只想关注苹果的价格,但是橘子的价格也推送给了小明,这对小明来说是不必要的困扰,所以我们可以增加一个key,让订阅者可以只订阅自己感兴趣的东西,代码如下:

var officialAccount = {}; // 定义公众号拥有者

officialAccount.clientList = {}; // 缓存列表

// 添加订阅者
officialAccount.listen = function (key, fun) {
  // 如果没有订阅过此类消息,新增一个缓存列表存放
  if (!this.clientList[key]) {
    this.clientList[key] = [];
  }
  this.clientList[key].push(fun); // 订阅的消息添加进缓存列表
};

// 发布消息
officialAccount.trigger = function () {
  // 取出消息的类型
  var key = Array.prototype.shift.call(arguments);
  // 取出该消息对应的回调函数集合
  var funs = this.clientList[key];

  // 如果没有订阅这类消息,直接返回不提示
  if (!funs || funs.length === 0) {
    return false;
  }

  for (let i = 0, fun; (fun = funs[i++]); ) {
    fun.apply(this, arguments);
  }
};

// 小明订阅消息
officialAccount.listen("苹果", function getPrice(price) {
  console.log("价格=", price);
});

// 小红订阅消息
officialAccount.listen("橘子", function getPrice(price) {
  console.log("价格=", price);
});

officialAccount.trigger("苹果", 200);
officialAccount.trigger("橘子", 300);

// ------以下为输出-----------
// 价格= 200
// 价格= 300

4 发布—订阅模式的通用实现

在上面的代码中我们实现了让一个公众号拥有了订阅和发布事件的能力,如果用户想在另一个公众号上订阅其他内容,这时我们可以将发布—订阅的功能提取出来,放在单独的对象中,这样可以快速给其他公众号添加订阅和发布的功能了,代码如下:

var eventExample= {
  clientList: [], // 订阅列表
  // 订阅事件
  listen: function (key, fun) {
    if (!this.clientList[key]) {
      this.clientList[key] = [];
    }
    this.clientList[key].push(fun);
  },
  // 发布事件
  trigger: function () {
    var key = Array.prototype.shift.call(arguments); // 获取订阅的key
    var funs = this.clientList[key]; // 取出订阅的事件列表

    // 如果没有订阅,不发消息
    if (!funs || funs.listen === 0) {
      return false;
    }

    for (let i = 0, fun; (fun = funs[i++]); ) {
      fun.apply(this, arguments);
    }
  },
};

再定义一个installEvent函数,这个函数可以给所有的对象都动态安装发布—订阅功能:

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

测试以下,我们给公众号A对象动态增加发布—订阅功能:

var A = {};
installEvent(A); // 安装发布—订阅功能
// 小明订阅消息
A.listen("苹果", function getPrice(price) {
  console.log("价格=", price);
});
// 小红订阅消息
A.listen("橘子", function getPrice(price) {
  console.log("价格=", price);
});
// 发布消息
A.trigger("苹果", 20);
A.trigger("橘子", 90);

5 取消订阅的事件

如果小明不想再关注苹果的价格了,他需要取消订阅之前的事件,我们给eventExample对象增加remove方法,用于帮助用户取消订阅事件:

eventExample.remove = function (key, fun) {
  var funs = this.clientList[key];
  // 如果这个key对应的消息没有人订阅,直接返回
  if (!funs) {
    return false;
  }
  // 如果没有传入具体的回调函数,则取消所有的订阅
  if (!fun) {
    funs && (funs.listen = 0);
  } else {
    // 反向遍历订阅的回调函数列表
    for (let l = funs.length - 1; l > 0; l--) {
      var _fun = funs[l];
      // 碰到相同的回调函数,则删掉取消订阅
      if (_fun === fun) {
        funs.splice(l, 1);
      }
    }
  }
};

6 全局的发布—订阅对象

在程序中,发布—订阅模式可以用一个全局的Event对象来实现,订阅者不需要了解消息来自哪个发布者,发布者也不知道消息会推送给哪些订阅者,Event作为一个类似“中介者”的角色,把订阅者和发布者联系起来,代码如下:

var eventExample = (function () {
  var clientList = {},
    listen,
    trigger,
    remove;
  listen = function (key, fun) {
    if (!clientList[key]) {
      clientList[key] = [];
    }
    clientList[key].push(fun);
  };
  trigger = function () {
    var key = Array.prototype.shift.call(arguments); // 获取订阅的key
    var funs = clientList[key]; // 取出订阅的事件列表

    // 如果没有订阅,不发消息
    if (!funs || funs.listen === 0) {
      return false;
    }

    for (let i = 0, fun; (fun = funs[i++]); ) {
      fun.apply(this, arguments);
    }
  };
  remove = function (key, fun) {
    var funs = this.clientList[key];
    // 如果这个key对应的消息没有人订阅,直接返回
    if (!funs) {
      return false;
    }
    // 如果没有传入具体的回调函数,则取消所有的订阅
    if (!fun) {
      funs && (funs.listen = 0);
    } else {
      // 反向遍历订阅的回调函数列表
      for (let l = funs.length - 1; l > 0; l--) {
        var _fun = funs[l];
        // 碰到相同的回调函数,则删掉取消订阅
        if (_fun === fun) {
          funs.splice(l, 1);
        }
      }
    }
  };
  return {
    listen,
    trigger,
    remove,
  };
})();

测试一下:

eventExample.listen("橘子", function (price) {
  console.log("价格=", price);
});
eventExample.trigger("橘子", 200); // 价格= 200

7 模块间通信

全局的发布—订阅模式,可以在两个封装良好的模块中进行通信,这两个模块可以完全不知道对方的存在。

比如现在有两个模块,a模块里面有一个按钮,每次点击按钮之后,b模块里的div中会显示按钮的总点击次数,我们用全局发布—订阅模式完成下面的代码,使得a模块和b模块可以在保持封装性的前提下进行通信,代码如下:

<button id="count">点击按钮</button>
<div id="show"></div>
<script>
  var a = (function () {
    var count = 0;
    var button = document.querySelector("#count");
    button.onclick = function () {
      eventExample.trigger("add", count++);
    };
  })();

  var b = (function () {
    var div = document.querySelector("#show");
    eventExample.listen("add", function (count) {
      div.innerHTML = count;
    });
  })();
</script>

请添加图片描述

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

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

相关文章

第五十六章 学习常用技能 - 执行 SQL 查询

文章目录 第五十六章 学习常用技能 - 执行 SQL 查询执行 SQL 查询检查对象属性 第五十六章 学习常用技能 - 执行 SQL 查询 执行 SQL 查询 要运行 SQL 查询&#xff0c;请在管理门户中执行以下操作&#xff1a; 选择系统资源管理器 > SQL。如果需要&#xff0c;请选择标题…

Servlet与设计模式

1 过滤器和包装器 过滤器可以拦截请求及控制响应&#xff0c;而servlet对此毫无感知。过滤器有如下作用&#xff1a; 1&#xff09;请求过滤器&#xff1a;完成安全检查、重新格式化请求首部或体、建立请求审计日志。 2&#xff09;响应过滤器&#xff1a;压缩响应流、追加或…

softmax激活函数

Softmax激活函数是一种用于多类别分类问题的激活函数&#xff0c;通常用于神经网络的输出层。它将原始分数&#xff08;也称为logits&#xff09;转换为表示概率分布的数值&#xff0c;使得每个类别的概率值都在0和1之间&#xff0c;并且所有类别的概率之和等于1。这使得它适用…

2023年9月 青少年软件编程等级考试Scratch二级真题

202309 青少年软件编程等级考试Scratch二级真题&#xff08;电子学会等级考试&#xff09; 试卷总分数&#xff1a;100分 试卷及格分&#xff1a;60 分 考试时长&#xff1a;60 分钟 第 1 题 点击绿旗&#xff0c;运行程序后&#xff0c;舞台上的图形是?( ) A&#xff1a;画…

极简c++(8)抽象类与多态

类型转换规则 父类定义的指针可以指向子类对象&#xff1b; 指针会误以为&#xff0c;他们指向的对象是Base1类型&#xff0c;导致错误&#xff1b; 虚函数定义 多态 如何实现多态&#xff1a; 1.创建类的继承关系图 2.所以类对象都可以调用的这个函数 3.创建父类指针数组 …

PyTorch 深度学习之卷积神经网络(高级篇)Advanced CNN(十)

0. Revision 前面讲的比较简单的是 串行网络结构 1. GoogLeNet 1.1 Inception module w h 要一致 what is 11 convolution? 信息融合-eg.高中各门学科成绩比较(总分) 最主要工作:改变通道数量 why is 11 convolution? 减少10倍 1.2 implementation of inception module 拼…

一文1400字从0到1进行Jmeter分布式压力测试【图文并茂】

1、场景 在做性能测试时&#xff0c;单台机器进行压测可能达不到预期结果。主要原因是单台机器压到一定程度会出现瓶颈。也有可能单机网卡跟不上造成结果偏差较大。 例如4C8G的window server机器&#xff0c;使用UI方式&#xff0c;最高压测在1800并发(RT 20ms以内)左右。如果…

在C语言中,单向链表的插入操作通常包括以下哪些步骤?

#科技新势力# #极简极速学编程# 【小白从小学Python、C、Java】 【计算机等级考试500强双证书】 【C语言每日一题】 在C语言中&#xff0c;单向链表的插入操作通常包括以下哪些步骤&#xff1f; A选项&#xff1a;将新节点指针->指向->链表最后一个节点 B选项&#xff1…

2024级199管理类联考之逻辑核心基础

且与或 含义 A且B(A^B):同时存在 常见形式 A并且B既A又B不但A而且B虽然A但是BA或B:二者至少有一个成立(即A且非B,非A且B,A且B) 否定形式 且的否定 A且B否定形式&#xff1a;非(A^B) 非A 或 非B非A且非B否定形式&#xff1a;非(非A^非B) A 或 B非A且B否定形式&#xff1a;…

c# xml 参数读取读取的简单使用

完整使用之测试参数的读取&#xff08;xml&#xff09; 保存一个xml文档&#xff08;如果没有就会生成一个默认的 里面的参数用的是我们默认设置的&#xff09;&#xff0c;之后每次更改里面的某项&#xff0c;然后保存 类似于重新刷新一遍。 这里所用的xml测试参数前面需要加…

淘宝天猫2023年双11红包活动入口在哪里活动时间是什么时候开始至什么时间结束2023年天猫淘宝双十一超级红包活动?

2023年淘宝天猫双11超级红包活动领取时间是从2023年10月24日20:00开始至11月11日23:59结束&#xff0c;淘宝天猫双十一活动时间内每天都可以领取1超级红包最高可得23888元。 2023年天猫淘宝双十一红包使用时间分为2个阶段&#xff1a;第一阶段是从2023年10月31日20:00开始至11…

螺杆支撑座对注塑机的生产过程有哪些重要影响?

螺杆支撑座对注塑机的生产过程具有重要影响&#xff0c;主要体现在以下几个方面&#xff1a; 1、精度和稳定性&#xff1a;螺杆支撑座能够提高注塑机的精度和稳定性&#xff0c;从而保证塑料制品的品质和一致性。通过提供稳定的支撑和承载&#xff0c;螺杆支撑座可以减少机器运…

高防CDN:网络安全的不可或缺之选

在当今数字化时代&#xff0c;网络攻击已经成为互联网上的一种不可避免的风险。为了应对不断升级的网络威胁&#xff0c;许多企业和组织正在采用高防御CDN&#xff08;Content Delivery Network&#xff09;技术&#xff0c;以确保他们的在线资产得到保护&#xff0c;用户体验得…

python 机器视觉 车牌识别 - opencv 深度学习 机器学习 计算机竞赛

1 前言 &#x1f525; 优质竞赛项目系列&#xff0c;今天要分享的是 &#x1f6a9; 基于python 机器视觉 的车牌识别系统 &#x1f947;学长这里给一个题目综合评分(每项满分5分) 难度系数&#xff1a;3分工作量&#xff1a;3分创新点&#xff1a;3分 &#x1f9ff; 更多资…

JDBC封装查询单个和查询多个

Mybatis在转化时候可以将数据库任意类型全转字符串是没有问题的 下面封装存在一个问题就是需要数据库字段类型与实体类字段类型一致 实体类 //String columnName metaData.getColumnName(i 1); 这个方法返回实际列名 String columnLabel metaData.getColumnLabel(i 1);//该…

查看系统的核心信息

查看系统的版本 cat /etc/redhat-release查看系统的主机名 hostname uname -n 查看内核 uname -r查看网卡信息 ip a ifconfig 查看网关 ip route route -n netstat -rn 查看分区black大小 df -h 查看磁盘block大小 df -i 查看磁盘和分区大小 fdisk -l查看内存大小…

竞赛选题 深度学习YOLOv5车辆颜色识别检测 - python opencv

文章目录 1 前言2 实现效果3 CNN卷积神经网络4 Yolov56 数据集处理及模型训练5 最后 1 前言 &#x1f525; 优质竞赛项目系列&#xff0c;今天要分享的是 &#x1f6a9; **基于深度学习YOLOv5车辆颜色识别检测 ** 该项目较为新颖&#xff0c;适合作为竞赛课题方向&#xff0…

Delay-Based 拥塞控制算法

上班七天了&#xff0c;有点崩溃&#xff0c;看一篇论文提神&#xff1a;A Delay-Based Approach for Congestion Avoidance in Interconnected Heterogeneous Computer networks&#xff0c;来自 Raj Jain&#xff0c;1989 年。这篇论文基于下图展开&#xff1a; 是不是很熟…

基于DBC Signal Group生成Autosar SR接口(2)

文章目录 前言m脚本生成BUS数据类型建立Input模块及关联对应的BUS数据类型实现效果总结 前言 上一篇文章中&#xff0c;介绍了DBC中SignalGroup的提取&#xff0c;对于已经提取好的Group信息&#xff0c;就可以批量操作生成Simulink BUS及Simulink接口模型了。本文介绍这部分的…