JavaScript发布订阅模式

什么是发布订阅模式?

订阅者把自己想订阅的事件注册到调度中心,当发布者发布该事件到调度中心,也就是该事件触发时,由调度中心统一通知订阅者注册到调度中心的处理代码。

JavaScript 对象的方式

1
<!DOCTYPE html>
2
<html lang="en">
3
  <head>
4
    <meta charset="UTF-8" />
5
    <meta http-equiv="X-UA-Compatible" content="IE=edge" />
6
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
7
    <title>策略模式</title>
8
    <style>
9
      .notify {
10
        width: 200px;
11
        height: 400px;
12
        border: 1px solid black;
13
        margin-top: 20px;
14
      }
15
    </style>
16
  </head>
17
  <body>
18
    <div>
19
      <button id="subscribe">订阅</button
20
      ><button id="unsubscribe">取消订阅</button><br /><br />
21
      <input type="text" placeholder="请输入值" id="input" /><br />
22
      <textarea class="notify" id="notify"></textarea>
23
    </div>
24
    <script>
25
      var Event = (function () {
26
        let list = {},
27
          subscribe,
28
          publish,
29
          unsubscribe;
30
        subscribe = function (key, fn) {
31
          if (!list[key]) {
32
            list[key] = [];
33
          }
34
          list[key].push(fn);
35
        };
36
        publish = function () {
37
          var key = Array.prototype.shift.call(arguments);
38
          var fns = list[key];
39
          if (!fns || fns.length == 0) return;
40
          for (let i = 0, fn; (fn = fns[i++]); ) {
41
            fn(...arguments);
42
          }
43
        };
44
        unsubscribe = function (key, fn) {
45
          var fns = list[key];
46
          if (!fns) return;
47
          if (!fn) {
48
            fns.length = 0;
49
          } else {
50
            for (let i = fns.length - 1; i >= 0; i--) {
51
              var _fn = fns[i];
52
              if ((_fn = fn)) {
53
                fns.splice(i, 1);
54
              }
55
            }
56
          }
57
        };
58
        return {
59
          subscribe,
60
          publish,
61
          unsubscribe,
62
        };
63
      })();
64
65
      // 绑定订阅事件
66
      document
67
        .getElementById("subscribe")
68
        .addEventListener("click", function () {
69
          Event.subscribe("notify", updateModel);
70
        });
71
72
      // 取消订阅
73
      document
74
        .getElementById("unsubscribe")
75
        .addEventListener("click", function () {
76
          Event.unsubscribe("notify");
77
        });
78
79
      // 通知订阅
80
      document.getElementById("input").addEventListener("input", function (e) {
81
        Event.publish("notify", e.target.value);
82
      });
83
84
      // 更新DOM
85
      function updateModel(value) {
86
        document.getElementById("notify").value = value;
87
      }
88
    </script>
89
  </body>
90
</html>

ES6 Class 实现

1
<!DOCTYPE html>
2
<html lang="en">
3
  <head>
4
    <meta charset="UTF-8" />
5
    <meta http-equiv="X-UA-Compatible" content="IE=edge" />
6
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
7
    <title>策略模式</title>
8
    <style>
9
      .notify {
10
        width: 200px;
11
        height: 400px;
12
        border: 1px solid black;
13
        margin-top: 20px;
14
      }
15
    </style>
16
  </head>
17
  <body>
18
    <div>
19
      <button id="subscribe">订阅</button><button id="unsubscribe">取消订阅</button><br><br>
20
      <input type="text" placeholder="请输入值" id="input" /><br />
21
      <textarea class="notify" id="notify"></textarea>
22
    </div>
23
    <script>
24
      class Event {
25
        constructor() {
26
          this.subscribers = {}; // // 一个数组,存储订阅者
27
        }
28
        // 注册/订阅,将订阅者添加到 subscribers 数组中
29
        subscribe(key, fn) {
30
          if (!this.subscribers[key]) {
31
            this.subscribers[key] = [];
32
          }
33
          this.subscribers[key].push(fn);
34
        }
35
        // 取消订阅。从 subscribers 数组中删除订阅者
36
        unsubscribe(key,fn) {
37
          let subscribers = this.subscribers[key];
38
          let list = [];
39
          // 不存在对应消息的 key
40
          if (!subscribers || subscribers.length === 0) return;
41
          // 如果没有回调函数,取消 key 对应消息的全部订阅
42
          if(!fn){
43
            this.subscribers[key] = [];
44
            return;
45
          }
46
          // 删除订阅消息的回调函数
47
          subscribers.forEach((item,index) => {
48
            if(item !== fn){
49
              list.push(fn)
50
            }
51
          })
52
          this.subscribers[key] = list;
53
          console.log(this.subscribers);
54
        }
55
        // 循环遍历 subscribers 数组中的每一个元素,并且调用它们注册时所提供的方法
56
        publish() {
57
          let key = Array.prototype.shift.call(arguments);
58
          let subscribers = this.subscribers[key];
59
          if (!subscribers || subscribers.length == 0) return;
60
          subscribers.forEach((item,index)=>{
61
            item(...arguments)
62
          })
63
        }
64
      }
65
66
      var publisher = new Event();
67
68
      // 绑定订阅事件
69
      document.getElementById("subscribe").addEventListener("click", function () {
70
        publisher.subscribe("notify", updateModel);
71
      });
72
73
      // 取消订阅
74
      document.getElementById('unsubscribe').addEventListener('click',function(){
75
        publisher.unsubscribe('notify')
76
      })
77
78
      // 通知订阅
79
      document.getElementById("input").addEventListener("input", function (e) {
80
        publisher.publish("notify", e.target.value);
81
      });
82
83
      // 更新DOM
84
      function updateModel(value){
85
        document.getElementById('notify').value = value
86
      }
87
88
    </script>
89
  </body>
90
</html>