观察者模式/发布订阅模式的应用

1,746 阅读4分钟

「这是我参与2022首次更文挑战的第27天,活动详情查看:2022首次更文挑战」。

前言

观察者模式发布订阅模式,在查阅资料时,有些文章中是将两者区分开来,还有些文章直接说两者就是一样的,就是一种模式。

两者的异同

既然叫两种名称,必然存在一些差异。首先看一下两种模式的流程图,还是能够比较直观的看到差异

图片.png

  • 察者模式(Observer)有两个对象,观察者对象(Observer)和目标对象(Subject),观察者观察或者说是订阅(Subscribe)目标,当目标发生变化,就通知(Fire Event)观察者去做更新的操作
  • 发布订阅模式(Publish/Subscribe)在这两个对象之间多了一个事件通道(Event Channel)作为中间过渡,目标对象发生了变化,不直接通知观察者,而是通过事件通道去通知观察者做更新,因此观察者也只需要订阅事件通道的消息即可

在宏观上来看,这两者都是一种比较适用于异步编程的范式,可以实现目标与观察者一对多的关系(收集依赖),在未来的某一时刻当被目标发生变化时,可以批量的通知观察者去做更新

观察者模式范式

//定义观察者模式类
function observe() {
  this.message = {};
}
//注册观察者
observe.prototype.regist = function (type, fn) {
  this.message[type] = fn;
}
//触发观察者
observe.prototype.fire = function (type) {
  this.message[type]();
}

regist 注册事件,通过调用 fire 方法一次性去触发事件

实际应用

案例:做一个抽奖页面,每转一圈速度减慢,最终停留在中奖元素上

思路是一开始就随机计算哪个元素中奖了,要做的就是控制动画的执行,转个若干圈,最终落到中间元素上

运用观察者模式,首先需要一个运动控制模块负责抽奖页面转盘的转动, 当检测到转盘转了一圈,就去通知观察者,重新再开始转动

  • 首先简单绘制一个抽奖页面,当前滚动到的元素高亮显示
<body>
  <style>
    // 元素
    .item {
      width: 50px;
      height: 50px;
      float: left;
      background: red;
      margin-right: 10px;
      text-align: center;
      line-height: 50px;
    }
    // 当前转到的元素高亮
    .item-on {
      border: 3px solid blue
    }
  </style>
  <div id="app"></div>
</body>
  // 初始化html,绘制页面
  function htmlInit(target) {
    for (var i = 0; i < COUNT; i++) {
      var _div = document.createElement('div');
      _div.setAttribute("class", "item");
      _div.innerHTML = i + 1;
      target.appendChild(_div);
      _domArr.push(_div);
    }
  }
  • 看到页面渲染出了元素 图片.png

  • 实现运动控制模块

  // 动画执行函数 
  function mover(moveConfig) {
    var nowIn = 0; // 当前元素
    var removeNum = COUNT - 1; // 前一个元素

    var timer = setInterval(function () {
      if (nowIn != 0) {
        removeNum = nowIn - 1;
      }
      
      // 清除前一个元素的样式
      _domArr[removeNum].setAttribute('class', 'item');
      // 滚动到元素添加样式
      _domArr[nowIn].setAttribute('class', 'item item-on');
      if(moveConfig.moveTime === 0) { // 当转到第一个元素上
        clearInterval(timer)
        return
      }
      nowIn++;
      if (nowIn == moveConfig.moveTime) {
        clearInterval(timer);
        if (moveConfig.moveTime == COUNT) { // 当转到最后一个元素上,说明还没转完
          observeOb.fire('finish'); // 通知在跑一圈
        }
      }
    }, moveConfig.speed);
  }
  // 动画控制
  function moveControll() {
    var final = getFinal();
    var _circle = Math.floor(final / 10, 0); //总圈数
    var _runCircle = 0; //已跑圈数
    var stopNum = final % COUNT; //最终停留元素
    var _speed = 50; // 初始化速度
    // 跑起来
    mover({
      moveTime: COUNT,
      speed: _speed
    });
    // 注册一圈转完以后需要做的事情
    observeOb.regist('finish', function () {
      var _time = 0;
      _speed += 50; // 速度变慢
      _runCircle++;
      if (_runCircle <= _circle) {
        _time = COUNT;
      } else { // 已经跑完多余圈数,最终停在结果元素上
        _time = stopNum;
      }
      // 继续跑
      mover({
        moveTime: _time,
        speed: _speed
      });
    })
  }
  • 查看最终效果 抽奖.gif

  • 附上完整代码

  //定义观察者模式类
  function observe() {
    this.message = {};
  }
  //注册观察者
  observe.prototype.regist = function (type, fn) {
    this.message[type] = fn;
  }
  //触发观察者
  observe.prototype.fire = function (type) {
    this.message[type]();
  }

  //实例化当前观察者对象
  var observeOb = new observe();

  var _domArr = []; //储存dom对象
  var COUNT = 10 // 对象个数
  var CIRCLE = 4 // 总圈数

  // 初始化html,绘制页面
  function htmlInit(target) {
    for (var i = 0; i < COUNT; i++) {
      var _div = document.createElement('div');
      _div.setAttribute("class", "item");
      _div.innerHTML = i + 1;
      target.appendChild(_div);
      _domArr.push(_div);
    }
  }
  // 最终结果,一共要跑几次
  function getFinal() {
    var _num = Math.random() * 10 + CIRCLE * COUNT;
    return Math.floor(_num, 0);
  }
  // 动画执行函数 
  function mover(moveConfig) {
    var nowIn = 0;
    var removeNum = COUNT - 1;

    var timer = setInterval(function () {
      if (nowIn != 0) {
        removeNum = nowIn - 1;
      }
      
      _domArr[removeNum].setAttribute('class', 'item');
      _domArr[nowIn].setAttribute('class', 'item item-on');
      if(moveConfig.moveTime === 0) { //转到第一个元素上
        clearInterval(timer)
        return
      }
      nowIn++;
      if (nowIn == moveConfig.moveTime) {
        clearInterval(timer);
        if (moveConfig.moveTime == COUNT) { // 当转到最后一个元素上,说明还没转完
          observeOb.fire('finish'); // 通知在跑一圈
        }
      }
    }, moveConfig.speed);
  }
  // 动画控制
  function moveControll() {
    var final = getFinal();
    var _circle = Math.floor(final / 10, 0); //总圈数
    var _runCircle = 0; //已跑圈数
    var stopNum = final % COUNT; //最终停留元素
    var _speed = 50; // 初始化速度
    // 跑起来
    mover({
      moveTime: COUNT,
      speed: _speed
    });
    // 注册一圈转完以后需要做的事情
    observeOb.regist('finish', function () {
      var _time = 0;
      _speed += 50; // 速度变慢
      _runCircle++;
      if (_runCircle <= _circle) {
        _time = COUNT;
      } else { // 已经跑完多余圈数,最终停在结果元素上
        _time = stopNum;
      }
      // 继续跑
      mover({
        moveTime: _time,
        speed: _speed
      });
    })
  }
  htmlInit(document.getElementById('app')); //初始化
  moveControll(); //执行动画