为了账号安全,请及时绑定邮箱和手机立即绑定

观察者模式

观察者模式

又称发布-订阅模式,指多个对象间存在一对多的依赖关系,当一个对象的状态发生改变时,所有依赖于它的对象都将得到通知。

应用场景

在 JavaScript 中,观察者模式最主要是应用在事件模型中:
事件模型可以分为两种,首先是原生的 Dom 事件,比如 click,这就是观察者模式的应用,相信大家一定使用过,在此就不再赘述。
其次是自定义事件,随着Vue、Angular等框架的流行,自定义事件也一定不会陌生,我们可以自己写一个自定义事件,来更好的理解观察者模式。

比如,有个购物车的功能,当点击增加或减少商品数量的按钮时,其相对应的数值和价格也会改变。

首先,我们创建一个观察者对象的基本模型:一个缓存列表,和三个方法,分别为订阅方法、发布方法以及取消方法。

const event = function () {
  // 缓存列表
  const _list = {};
  return {
    // 订阅方法
    listen() {},
    // 发布方法
    dispatch() {},
    // 取消方法
    remove() {},
  };
};

接下来,就是实现这三个方法。

订阅方法:

// 参数 type:自定义事件名称
// 参数 fn:与 type 相对应的处理方法
listen(type, fn) {
  // 如果缓存列表中没有该事件,则注册一个
  if (Object.is(_list[type], undefined)) {
    _list[type] = [];
  }
   // 如果缓存列表中有该事件,则把处理方法保存到队列中
  _list[type].push(fn);
}

发布方法:

// 参数 type:自定义事件名称
// 参数 args:函数参数
dispatch(type, ...args) {
  // 如果缓存列表中没有该事件或者该事件没有可执行的方法,则退出
  if (!_list[type] || _list[type].length === 0) {
    return false;
  }
  // 否则依次执行该事件的处理方法
  for (let fn of _list[type]) {
    fn(...args);
  }
}

取消方法:

// 参数 type:自定义事件名称
// 参数 fn:与 type 相对应的处理方法
remove(type, fn) {
  // 如果缓存列表中有该事件,并且该事件有可执行的方法,则进行以下操作
  if (Array.isArray(_list[type]) && _list[type].length > 0) {
    // 如果没有指定某个处理方法,则全部清空
    if (!fn) {
      _list[type] = [];
    } else {
    // 如果指定了取消某个处理方法,则相应删除
      _list[type] = _list[type].filter((v) => {
        return v !== fn;
      });
    }
  }
}

最后,我们去实现购物车的功能:

例子:

<button id="start">开始编辑</button>
<br />
<br />
<br />
<button id="inc" disabled>+</button>
<span>台灯</span>
<button id="dec" disabled>-</button>
<p>数量:<strong id="amount">1</strong></p>
<p>价格:<strong id="price">50</strong></p>
<br />
<br />
<br />
<button id="stop" disabled>完成</button>

<script>
  const $event = (function () {
    // 缓存列表
    const _list = {};
    return {
      // 订阅方法
      listen(type, fn) {
        if (Object.is(_list[type], undefined)) {
          _list[type] = [];
        }
        _list[type].push(fn);
      },
      // 发布方法
      dispatch(type, ...args) {
        if (!_list[type] || _list[type].length === 0) {
          return false;
        }
        for (let fn of _list[type]) {
          fn(...args);
        }
      },
      // 取消方法
      remove(type, fn) {
        if (Array.isArray(_list[type]) && _list[type].length > 0) {
          if (!fn) {
            _list[type] = [];
          } else {
            _list[type] = _list[type].filter((v) => {
              return v !== fn;
            });
          }
        }
      },
      // 观察缓存列表的数据变化
      show() {
        console.log(_list);
      },
    };
  })();

  const startBtn = document.querySelector('#start');
  const stopBtn = document.querySelector('#stop');
  const incBtn = document.querySelector('#inc');
  const decBtn = document.querySelector('#dec');
  const amount = document.querySelector('#amount');
  const price = document.querySelector('#price');
  let amountNum = 1;
  let priceNum = 50;
  const incFn = (n) => {
    amountNum++;
    priceNum = amountNum * 50;
    amount.innerHTML = amountNum;
    price.innerHTML = priceNum;
    console.log(n);
  };
  const decFn = (n) => {
    if (amountNum === 1) {
      return false;
    }
    amountNum--;
    priceNum = priceNum - 50;
    amount.innerHTML = amountNum;
    price.innerHTML = priceNum;
    console.log(n);
  };
  const changeBtnStates = (items = [startBtn, stopBtn, incBtn, decBtn]) => {
    items.forEach((item) => {
      typeof item.getAttribute('disabled') === 'string'
        ? item.removeAttribute('disabled')
        : item.setAttribute('disabled', 'disabled');
    });
  };

  // 点击“开始编辑”按钮,订阅消息
  startBtn.addEventListener('click', () => {
    // 订阅自定义事件 inc
    $event.listen('inc', incFn);

    // 订阅自定义事件 dec
    $event.listen('dec', decFn);

    // 其他代码,不是重点
    $event.show();
    changeBtnStates();
  });

  // 点击“+”按钮,发布(触发)自定义事件 inc
  incBtn.addEventListener('click', () => {
    $event.dispatch('inc', '+1');
  });
  // 点击“-”按钮,发布(触发)自定义事件 dec
  decBtn.addEventListener('click', () => {
    $event.dispatch('dec', '-1');
  });

  // 点击“完成”按钮,取消订阅
  stopBtn.addEventListener('click', () => {
    // 取消自定义事件 inc
    $event.remove('inc', incFn);
    // 取消自定义事件 dec
    $event.remove('dec', decFn);

    // 其他代码,不是重点
    $event.show();
    changeBtnStates();
  });
</script>

观察者模式的意义

看了上面的例子,很多人会觉得,本来简单的功能,使用观察者模式反而变得复杂,意义何在?
其实,观察者模式的意义可以从两个方面体现出来:

  • 在 JavaScript 开发中,观察者模式被广泛应用在异步编程中。当我们通过 Ajax 和后台交互时,想在交互完成之后对数据做一些处理,那我们可以订阅一个事件,然后在请求完成之后发布这个事件。
  • 观察者模式起到了松耦合的作用,发布者与订阅者之间可以相互通信,但又不会彼此影响,只要事件名称没有变化,就可以自由地改变它们。

如有错误,欢迎指正,本人不胜感激。

点击查看更多内容
TA 点赞

若觉得本文不错,就分享一下吧!

评论

作者其他优质文章

正在加载中
  • 推荐
  • 评论
  • 收藏
  • 共同学习,写下你的评论
感谢您的支持,我会继续努力的~
扫码打赏,你说多少就多少
赞赏金额会直接到老师账户
支付方式
打开微信扫一扫,即可进行扫码打赏哦
今天注册有机会得

100积分直接送

付费专栏免费学

大额优惠券免费领

立即参与 放弃机会
意见反馈 帮助中心 APP下载
官方微信

举报

0/150
提交
取消