观察者模式

178 阅读3分钟

观察者模式

说起观察者模式,离不开发布者订阅者

举个生活中的例子:刷微博

我们会在微博中关注一些博主,当这些博主要更新内容时,会将内容发布到平台上,平台再将内容推送给我们。

我们就是订阅者博主则是发布者。

使用场景

组件通信、消息监听、

代码中的实现

简易版的观察者模式

// 发布者类
class Publisher {
  constructor(name) {
    this.name = name;
    this.observers = []
   }
​
  // 增加订阅者
  add(observer) {
    this.observers.push(observer);
  }
  // 取消订阅
  remove(observer) {
    const index = this.observers.findIndex(o => o === observer);
    this.observers.splice(index, 1);
  }
  // 发布消息
  notify() {
    this.observers.forEach(observer => {
      observer.do(this.name);
    });
  }
}
// 订阅者类
class Observe {
  constructor(name) {
    this.name = name;
  }
  do(name) {
    console.log(`${this.name}又看到${name}的新瓜啦!`)
  }
}
const yxfan = new Observe('yxfan');
const xiaopa = new Observe('xiaopa');
const krisWu = new Publisher('吴亦凡');
krisWu.add(yxfan);
krisWu.add(xiaopa);
krisWu.notify();

yxfanxiaopa订阅了krisWu的动态,所以当krisWu发布新瓜时,两位订阅者就能接受到。

所以打印:yxfan又看到吴亦凡的新瓜啦!`` xiaopa又看到吴亦凡的新瓜啦!

原谅我不厚道的笑了。。。

实现Event Emitter

Emitter在开发中经常用(典型的事件发布者-订阅者模式),不管是Vue合适React,涉及到组件通信时,用Emitter将会方便许多(父子组件通信大可不必)。

下面实现一个简易版的Emitter

class Emitter {
  // 事件对象
  handlers = {}
  // 添加事件
  on(eventName, cb) {
    if (this.handlers[eventName]) {
      this.handlers[eventName].push(cb);
    } else {
      this.handlers[eventName] = [cb];
    }
  }
  // 触发事件
  emit(eventName, ...params) {
    if (this.handlers[eventName]) {
      return this.handlers[eventName].forEach(cb => {
        cb(...params);
      });
    }
    throw `${eventName}事件不存在`
  }
  // 删除事件
  remove(eventName) {
    if (this.handlers[eventName]) {
      return delete this.handlers[eventName];
    }
    throw `${eventName}事件不存在`
  }
  // 单次监听器,用完即删除
  once(eventName, cb) {
    this.on(eventName, (...args) => {
      cb(...args);
      this.remove(eventName);
    });
  }
}
const emitter = new Emitter();
window.emitter = emitter;
window.emitter.on('emit-test', () => {
  console.log(`中国又夺冠🏆啦`)
});
window.emitter.on('emit-test', (num) => {
  console.log(`现在金牌${num}枚`)
});
try {
  window.emitter.emit('emit-test', 15);
  console.log(emitter)
  window.emitter.remove('emit-test');
  window.emitter.emit('emit-test', 15);
} catch (e) {
  console.error(e)
}

打印结果:

中国又夺冠🏆啦
现在金牌15枚
Emitter {
  handlers: {}
}
emit-test事件不存在

原理很简单,从始至终只需将Emitter实例化一次,并挂载在window上,使得任何地方都可以访问到。

监听时只需要把事件名丢进缓存池handles中,做一个eventNamecb的映射,触发时找到缓存池中的eventName并运行该映射下的方法即可。

观察者模式与发布者-订阅者模式

上述例子中分别实现了观察者模式发布者-订阅者模式,好像两者并没有什么区别。

上述模式都是为了实现模块间的解耦,但观察者模式又没有完全解耦。被观察者必须去维护一套观察者的集合,这些观察者必须实现统一的方法供被观察者调用,两者之间还是有着说不清、道不明的关系。

我们用上述两个例子来分析这段话

1、被观察者必须去维护一套观察者的集合

krisWu.add(yxfan); krisWu.add(xiaopa); 被观察者krisWu是知道它的观察者有哪些的,并调用add()把观察者yxfanxiaopa添加进了自己的观察者集合observers中。

2、观察者必须实现统一的方法供被观察者调用

观察者yxfanxiaopa中都有do() 在被观察者krisWunotify()中调用。

而发布者-订阅者就没有那么多戏了,发布者并不知道它的订阅者有哪些,事件的注册和触发都发生在第三方平台上(事件总线),实现了完全的解耦。

区分这两者模式,就看有木有用到第三方平台。