在 JS 中,事件处理函数的绑定 addEventListener 就是发布订阅的模式。
addEventListener 由 EventTarget 对象提供。

方式

  1. 主题:发布者
  2. 主题上要有回调的队列
  3. 依次调用回调队列中的方法

    优势

  4. 从空间上解耦

    1. 分开主题与调用方法
  5. 从时间上解耦
    1. 可以先预想到有什么结果,可以设计做什么

      高内聚,低耦合

      • 耦合是代码之间的相互依赖性
      • 内聚是代码的重用性

例子

  1. var salesOffices = {}; // 售楼部
  2. salesOffices.clientList = []; // 花名册
  3. salesOffices.listen = function(fn) {
  4. this.clientList.push(fn);
  5. }
  6. salesOffices.trigger = function() {
  7. for(var i = 0; i < this.clientList.length; i++) {
  8. var fn = this.clientlist[i];
  9. fn.apply(this, arguments);
  10. }
  11. }
  12. salesOffices.listen(function(price, square) {
  13. console.log('zhangsan', price, square);
  14. });
  15. salesOffices.listen(function(price, square) {
  16. console.log('zhaosi', price, square);
  17. });
  18. setTimeout(() => {
  19. salesOffices.trigger(2000, 80);
  20. salesOffices.trigger(3000, 110);
  21. })

会出现一个问题,zhangsan 只关注价格 2000 以下的,zhaosi 只关注价格 3000 以上的。所以要增加一个标识来区分。

  1. var salesOffices = {}; // 售楼部
  2. salesOffices.clientList = []; // 花名册
  3. salesOffices.listen = function(key, fn) {
  4. if(!this.clientList[key]) {
  5. this.clientList[key] = [];
  6. }
  7. this.clientList[key].push(fn);
  8. }
  9. salesOffices.trigger = function() {
  10. var key = Array.prototype.shift.call(arguments),
  11. fns = this.clientList[key];
  12. for(var i = 0; i < this.fns.length; i++) {
  13. var fn = this.clientlist[i];
  14. fn.apply(this, arguments);
  15. }
  16. }
  17. salesOffices.remove = function(key, fn) {
  18. var fns = this.clientList[key];
  19. if(!fns){
  20. return false;
  21. }
  22. for(var i = 0; i < fns.length; i++) {
  23. var _fn = fns[i];
  24. if(_fn === fn) {
  25. fns.splice(i, 1);
  26. }
  27. }
  28. }
  29. salesOffices.listen('2000meter', function(price, square) {
  30. console.log('zhangsan', price, square);
  31. });
  32. salesOffices.listen('3000meter', function(price, square) {
  33. console.log('zhaosi', price, square);
  34. });
  35. setTimeout(() => {
  36. salesOffices.trigger('2000meter', 2000, 80);
  37. salesOffices.trigger('3000meter', 3000, 110);
  38. })

实现

  1. // 订阅者
  2. class Subscriber {
  3. constructor() {
  4. this.publishers = [];
  5. }
  6. // 订阅发布者
  7. subscribe(publisher) {
  8. this.publishers.push(publisher);
  9. }
  10. // 通知
  11. notify() {
  12. this.publishers.forEach(publisher => {
  13. publisher.update();
  14. })
  15. }
  16. }
  17. // 发布者
  18. class Publisher {
  19. constructor(callback) {
  20. this.callback = callback;
  21. }
  22. update() {
  23. this.callback();
  24. }
  25. }
  26. // 创建订阅者
  27. const sub = new Subscriber();
  28. // 创建发布者
  29. const publisher1 = new Publisher(() => console.log('Publisher1'));
  30. const publisher2 = new Publisher(() => console.log('Publisher2'));
  31. // 订阅发布者
  32. sub.subscribe(publisher1);
  33. sub.subscribe(publisher2);
  34. // 触发通知
  35. sub.notify();