首页 设计模式 02 — 观察者模式
文章
取消

设计模式 02 — 观察者模式

1. 定义

《JavaScript设计模式与开发实践》中对观察者模式的定义如下:

发布—订阅模式又叫观察者模式,它定义对象间的一种一对多的依赖关系,当一个对象的状态发生改变时,所有依赖于它的对象都将得到通知。在JavaScript 开发中,我们一般用事件模型来替代传统的发布—订阅模式。

2. 案例

Vue2 中有四个事件相关的 API:

  1. vm.$on:监听当前实例上的自定义事件。
  2. vm.$once:监听一个自定义事件,但是只触发一次。
  3. vm.$off:移除自定义事件监听器。
  4. vm.$emit:触发当前实例上的事件。

这四个 API 不能跨组件通信,下一节我们将其作为参考实现全局的发布-订阅模式

3. 简单实现

发布—订阅模式可以用一个全局的 Event 对象来实现,订阅者不需要了解消息来自哪个发布者,发布者也不知道消息会推送给哪些订阅者,Event 作为一个类似“中介者”的角色,把订阅者和发布者联系起来。见如下代码:

JS

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
const Events = (function() {
  const events = {};
  return {
    on: on,
    once: once,
    off: off,
    emit: emit
  }

  /**
  *  监听
  *  @params {string | Array<string>} event 事件名或包含多个事件名的数组
  *  @params {Function} fn 回调函数
  */
  function on(event, fn) {
    if (!event) return;
    if (Array.isArray(event)) {
      for (let i = 0, len = event.length; i < len; i++) {
        this.on(event[i], fn);
      }
    } else {
      (events[event] || (events[event] = [])).push(fn);
    }
  }

  /**
  *  监听一次。一旦触发之后,监听器就会被移除。
  *  @params {string | Array<string>} event 事件名或包含多个事件名的数组
  *  @params {Function} fn 回调函数
  */
  function once(event, fn) {
    if (Array.isArray(event)) {
      for (let i = 0, len = event.length; i < len; i++) {
        this.once(event[i], fn);
      }
    } else {
      const fnWrapper = function() {
        // 注意这里取消监听的是 fnWrapper ,而不是 fn
        this.off(event, fnWrapper);
        fn.apply(this, arguments);
      }
      this.on(event, fnWrapper);
    }
  }

  /**
  *  取消监听
  *  @params {string | Array<string>} event 事件名或包含多个事件名的数组
  *  @params {Function} fn 取消监听的回调函数
  */
  function off(event, fn) {
    if (!event) return;
    if (Array.isArray(event)) {
      for (let i = 0, len = event.length; i < len; i++) {
        this.off(event[i], fn);
      }
    } else {
      if (!fn) return events[event] = [];
      const eventsList = events[event];
      for (let i = 0, len = eventsList.length; i < len; i++) {
        if (eventsList[i] === fn) {
          eventsList.splice(i, 1);
          break;
        }
      }
    }
  }

  /**
  *  触发事件
  *  @params {string } event 触发的事件名
  *  @params {Array} [args] 剩余参数
  */
  function emit(event) {
    const eventsList = events[event] || [];
    const args = Array.prototype.slice.call(arguments, 1); // 移除第一个参数
    eventsList.forEach(ev => {
      ev.apply(this, args);
    });
  }
})();
本文由作者按照 CC BY 4.0 进行授权