前言

很多人都会认为观察者(Observer)模式等同于发布(Publish)/订阅(Subscribe)模式,发布订阅模式里的Publisher 就是观察者模式里的 Subject,而 Subscriber,就是 Observer。实际上,它们的实现思路是非常相似的,但是其流程结构是存在区别的。发布订阅模式可以说是派生自观察者模式的。

观察者模式

image.png

每一个观察者(Observer)实际上是一个实例对象,Subject 的通知实际上是调用观察者中的某个具体的方法,即其内部的更新接口。发布者和观察者实际上是存在依赖的,其必须知道观察者内部更新的接口方法是什么。观察者模式实现的是一种松散耦合。

因此观察者模式多用于单个应用内部

// 发布者,发布者拥有观察者列表
class Subject{
  constructor(){
    this.observerList = [];// 观察者列表
	}
	addObserver(observer){
  	this.observerList.push(observer);
	}
	removeObserver(observer){
  	for (let i = 0; i < this.observerList.length; i++) {
      // 判断是否是源函数,这里考虑了 once 的特殊情况
      if (this.observerList[i] === observer || this.observerList[i].source === observer) {
        this.observerList.splice(i, 1);
        break;
      }
    }
	}
	notify(data){
    this.observerList.forEach((observer) => {
    	observer.update(data);
    })
	}
}
// 观察者,每个观察者都需要一个更新接口的方法
class Observer{
  constructor(){
    
  }
  update(){
    // ...
  };
}

发布订阅模式

发布订阅.png

发布订阅模式的订阅者实际上就是一个函数或者说方法,在发布者进行发布时,并不是直接通知订阅者,而是由调度中心(broker)来逐一调用订阅者,发布者不需要管订阅者是谁。也就是说发布订阅模式中,发布者和订阅者不是松耦合,而是完全解耦的。

发布订阅模式更像浏览器的事件机制,用户触发事件的发布,但是其不知道会发生什么事情,由浏览器进行对该事件的事件池进行逐一调用。

因此发布订阅模式多用于不同模块之间的解耦与通信。相比之下,发布订阅模式更为常用。

// 这是改进版的发布订阅模式
class Subscribe {
  //=> []创建一个容器,管理需要执行的方法
  //=> {} 实现多个不同类型容器
  constructor() {
    this.ponds = {};
  }
  //=> 订阅
  on (type, listener) {
    // listener 必须是函数
    if (typeof listener !== "function")
      throw new error("the second param of 'on' must be a function");
    this.ponds[type] = this.ponds[type] || [];

    // 判断事件池中是否已存在相同的 listener,存在则不添加
    let n = this.ponds[type].indexOf(listener);
    if (n === -1) {
      this.ponds[type].push(listener);
    }

    return this;
  }

  //=> 订阅一次
  once (type, listener) {
    if (typeof listener !== "function")
      throw new error("the second param of 'once' must be a function");

    let _this = this;
    let fn = () => {
      // this 为 window
      _this.off(type, listener);
      listener.apply(_this, arguments);
    }

    fn.source = listener; // 将源函数挂载到 fn 上

    return this.on(type, fn);
  }

  //=> 执行容器中所有的方法
  // 参数为 type, ...args
  emit (...args) {
    let type = args.shift();

    let listeners = this.ponds[type];
    if (!listeners) return;

    // 锁死队列,防止事件池中的函数不断向事件池添加订阅,出现死循环
    listeners = listeners.slice();

    // 进行逐个发布
    listeners.forEach((item) => {
      item(...args);
    })

    return this;
  }

  //=> 取消订阅
  off (type, listener) {
    let listeners = this.ponds[type];
    if (!listeners) return this;
    for (let i = 0; i < listeners.length; i++) {
      // 判断是否是源函数,这里考虑了 once 的特殊情况
      if (listeners[i] === listener || listeners[i].source === listener) {
        listeners.splice(i, 1);
        break;
      }
    }
    if (listeners.length === 0) {
      delete this.ponds[type]; // 防止空的时候还进行遍历判断
    }
    return this;
  }

  //=> 获取所有的订阅者
  listeners (type) {
    // 返回克隆数组
    return (this.ponds[type] || []).slice();
  }
}
生命的意义不仅是活着,而是我们给别人的生命带来了何种不同。

© 2016-2019 destiny