小工具知识网

当前位置: 首页 > 行业知识 > 【Javascript】设计模式之发布订阅模式

行业知识

【Javascript】设计模式之发布订阅模式

2026-11-03 15:33:09 来源:互联网转载

文章目录

  • 1、现实中的发布-订阅模式
  • 2、DOM 事件
  • 3、简单的发布-订阅模式
  • 4、通用的发布-订阅模式
  • 5、先发布再订阅
  • 6、小���

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

    ()

    1、现实中的发布-订阅模式

    小明最近看上了一套房子,到了售楼处之后才被告知,该楼盘的房子早已售罄。好在售楼

    处告诉小明,不久后还有一些尾盘推出。于是小明离开之前,把电话号码留在了售楼处,相同的还有小红,小强。于是新楼盘推出的时候,售楼处会翻开花名册,遍历上面的电话号码,依次发送一条短信来通知他们

    ()

    2、DOM 事件

    只要我们曾经在 DOM 节点上面绑定过事件函数,那我们就曾经使用过发布—订阅模式

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

    3、简单的发布-订阅模式

    发布-订阅模式的实现步骤

    1、定义发布者

    2、给发布者添加一个缓存列表,用于存放回调函数以便通知订阅者

    3、最后发布消息的时候,发布者会遍历这个缓存列表,依次触发里面存放的订阅者回调函数

    代码示例:

    var salesOffices = {}; // 定义发布者salesOffices.clientList = []; // 缓存列表,存放订阅者的回调函数salesOffices.listen = function (fn) {  // 增加订阅者  this.clientList.push(fn); // 订阅的消息添加进缓存列表};salesOffices.trigger = function () {  // 发布消息  for (var i = 0; i  

    测试:

    salesOffices.listen(function (price, squareMeter) {  // 小明订阅消息  console.log('小明价格= ' + price);  console.log('小明squareMeter= ' + squareMeter);});salesOffices.listen(function (price, squareMeter) {  // 小红订阅消息  console.log('小红价格= ' + price);  console.log('小红squareMeter= ' + squareMeter);});salesOffices.trigger(2000, 300);salesOffices.trigger(2000, 700);

    问题:

    订阅者接收到了发布者发布的每个消息,有些并不是订阅者需要的

    解决:

    要增加一个标示 key,让订阅者只订阅自己感兴趣的消息:

    改写代码:

    var salesOffices = {}; // 定义发布者salesOffices.clientList = {}; // 缓存对象,存放订阅者的回调函数salesOffices.listen = function (key, fn) {  if (!this.clientList[key]) {    // 如果还没有订阅过此类消息,给该类消息创建一个缓存列表    this.clientList[key] = [];  }  this.clientList[key].push(fn); // 订阅的消息添加进消息缓存列表};salesOffices.trigger = function () {  // 发布消息  var key = Array.prototype.shift.call(arguments); // 取出消息类型  var fns = this.clientList[key]; // 取出该消息对应的回调函数集合  if (!fns || fns.length === 0) {    // 如果没有订阅该消息,则返回    return false;  }  for (var i = 0; i  

    测试:

    salesOffices.listen('squareMeter88', function (price) {  // 小明订阅 88 平方米房子的消息  console.log('价格= ' + price); // 输出: 2000000});salesOffices.listen('squareMeter110', function (price) {  // 小红订阅 110 平方米房子的消息  console.log('价格= ' + price); // 输出: 3000000});salesOffices.trigger('squareMeter88', 30000);salesOffices.trigger('squareMeter110', 70000);

    4、通用的发布-订阅模式

    包含:发布-订阅,取消订阅

    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);    var fns = this.clientList[key];    if (!fns || fns.length === 0) {      return false;    }    for (var i = 0, fn; (fn = fns[i++]); ) {      fn.apply(this, arguments);    }  },  // 增加 remove 方法  remove(key, fn) {    var fns = this.clientList[key];    if (!fns) {      return false;    }    if (!fn) {      fns && (fns.length = 0);    } else {      for (var i = fns.length - 1; i >= 0; i--) {        var _fn = fns[i];        if (fn === _fn) {          fns.splice(i, 1);        }      }    }  },};

    测试:

    var f1 = function (price) {  console.log('价格= ' + price);};Event.listen('s88', f1);var f2 = function (price) {  console.log('价格= ' + price);};Event.listen('s110', f2);Event.remove('s110', f2); // 删除订阅Event.trigger('s88', 30000);Event.trigger('s110', 70000);

    5、先发布再订阅

    应用场景:发布者发布的内容,不管订阅者在发布之前订阅,或者发布之后订阅,都可触发订阅者订阅的内容

    代码:

    var Event = (function () {  var clientList = {};  var offlineStack = {}; // 离线缓存参数  var triggerStack = {}; // 已触发trigger的参数缓存  var listen;  var trigger;  var remove;  listen = function (key, fn) {    if (!clientList[key]) {      clientList[key] = [];    }    clientList[key].push(fn);    // 如果此时订阅的事件,已经发布了,则自定触发一次订阅内容(fn)    if (triggerStack[key]) {      fn.apply(this, triggerStack[key]);    } else if (offlineStack[key]) {      // 如果是离线状态,则触发事件      fn.apply(this, offlineStack[key]);    }  };  trigger = function () {    var key = Array.prototype.shift.call(arguments);    var fns = clientList[key];    if (fns) {      // 已经有人订阅此事件,将参数缓存      //(假如有些订阅者比较晚订阅,且发布者已经发布过了,那么这个订阅者订阅的时候,自动触发一次订阅内容)      triggerStack[key] = [...arguments];      for (var i = 0; i = 0; l--) {        var _fn = fns[l];        if (_fn === fn) {          fns.splice(l, 1);        }      }    }  };  return {    listen: listen,    trigger: trigger,    remove: remove,  };})();

    测试1:先订阅,再发布

    // 先订阅Event.listen('test1', function (a) {    console.log('我是发布之前的订阅者1:', a);});Event.listen('test1', function (a) {    console.log('我是发布之前的订阅者2:', a);});// 再发布Event.trigger('test1', 12);// 我是发布之前的订阅者1: 12// 我是发布之前的订阅者2: 12

    测试2:先发布,再订阅

    // 先发布Event.trigger('test1', 12);// 再订阅Event.listen('test1', function (a) {  console.log('我是发布之后的订阅者1:', a);});Event.listen('test1', function (a) {  console.log('我是发布之后的订阅者2:', a);});// 我是发布之后的订阅者1: 12// 我是发布之后的订阅者2: 12

    测试3:先订阅,再发布,再订阅

    // 先订阅Event.listen('lis1', function (a) {    console.log('我是发布之前的订阅者1:', a);})Event.listen('lis1', function (a) {    console.log('我是发布之前的订阅者2:', a);})// 再发布console.log('---第1次发布');Event.trigger('lis1', 123);console.log('---第1次发布完成');// 再订阅Event.listen('lis1', function (b) {    console.log('我是发布之后的订阅者~:', b);})// ---第1次发布// 我是发布之前的订阅者1: 123// 我是发布之前的订阅者2: 123// ---第1次发布完成// 我是发布之后的订阅者~: 123

    测试4:先发布,再订阅,再发布,再订阅

    // 先发布console.log('------第1次发布-------');Event.trigger('lis1', 123);// 再订阅Event.listen('lis1', function (a) {    console.log('我是发布之后的订阅者1:', a);})Event.listen('lis1', function (a) {    console.log('我是发布之后的订阅者2:', a);})// 再发布console.log('------第2次发布-------');Event.trigger('lis1', 456);// 再订阅Event.listen('lis1', function (a) {    console.log('我是发布之后的再次订阅者1:', a);})Event.listen('lis1', function (a) {    console.log('我是发布之后的再次订阅者2:', a);})// ------第1次发布-------// 我是发布之后的订阅者1: 123// 我是发布之后的订阅者2: 123// ------第2次发布-------// 我是发布之后的订阅者1: 456// 我是发布之后的订阅者2: 456// 我是发布z之后的再次订阅者1: 456// 我是发布z之后的再次订阅者2: 456

    测试5:先订阅,再发布,再订阅,再发布

    Event.listen('lis1', function (a) {    console.log('我是发布之前的订阅者1:', a);})Event.listen('lis1', function (a) {    console.log('我是发布之前的订阅者2:', a);})console.log('---第1次发布');Event.trigger('lis1', 123);console.log('---第1次发布完成');Event.listen('lis1', function (b) {    console.log('我是发布之后的订阅者~:', b);})console.log('---第2次发布');Event.trigger('lis1', 456);console.log('---第2次发布完成');// ---第1次发布// 我是发布之前的订阅者1: 123// 我是发布之前的订阅者2: 123// ---第1次发布完成// 我是发布之后的订阅者~: 123// ---第2次发布// 我是发布之前的订阅者1: 456// 我是发布之前的订阅者2: 456// 我是发布之后的订阅者~: 456// ---第2次发布完成

    6、小结

    优点:

    一为时间上的解耦,二为对象之间的解耦

    缺点:

    1、创建订阅者本身要消耗一定的时间和内存,而且当你订阅一个消息后,也许此消息最后都未发生,但这个订阅者会始终存在于内存中

    2、如果过度使用的话,对象和对象之间的必要联系也将被深埋在背后,会导致程序难以跟踪维护和理解

    应用:

    应用非常广泛,既可以用在异步编程中,也可以帮助我们完成更松耦合的代码编写

上一篇:服务器双系统怎么进?

下一篇:[记录]基于Flask Web全栈开发实战-项目实战·上篇(黄勇 • 著)