订阅发布模式和EventEmitter

Node中的EventEmitter,以及Vue的双向绑定,还有DOM2的事件监听方法都采用了发布订阅模式,所以我们动手实现一下,达成如下效果:

var emitter = new EventEmitter();

function handleOne(a, b, c) {
    console.log('第一个监听函数', a, b, c)
}

function handleSecond(a, b, c) {
    console.log('第二个监听函数', a, b, c)
}

function handleThird(a, b, c) {
    console.log('第三个监听函数', a, b, c)
}

emitter.on("demo", handleOne)
    .once("demo", handleSecond)
    .on("demo", handleThird);

emitter.emit('demo', [1, 2, 3]);
// => 第一个监听函数 1 2 3
// => 第二个监听函数 1 2 3
// => 第三个监听函数 1 2 3

emitter.off('demo', handleThird);
emitter.emit('demo', [1, 2, 3]);
// => 第一个监听函数 1 2 3

emitter.allOff();
emitter.emit('demo', [1, 2, 3]);
// nothing
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
  • 每次调用方法则return this实现链式调用
  • once表示只触发一次
  • on表示监听某个事件
  • emit触发这个事件
  • off表示不监听某个事件
  • alloff表示移除所有事件

实现方式:

class EventEmitter {
  constructor() {
    this._Events = new Map();
  }
  on(type, handler) {
    if (this._Events.has(type)) {
      this._Events.get(type).push({
        listener: handler,
        once: false
      });
    } else {
      this._Events.set(type, [
        {
          listener: handler,
          once: false
        }
      ]);
		}
		return this;
  }
  once(type, handler) {
    if (this._Events.has(type)) {
      this._Events.get(type).push({
        listener: handler,
        once: true
      });
    } else {
      this._Events.set(type, [
        {
          listener: handler,
          once: true
        }
      ]);
		}
		return this;
  }
  off(type, handler) {
    if (this._Events.has(type)) {
      let index = this._Events.get(type).findIndex(item => {
        return item.listener === handler;
      });
      this._Events.get(type).splice(index, 1);
		}
		return this;
  }
  emit(type, ...args) {
    if (this._Events.has(type)) {
      this._Events.get(type).forEach(item => {
        item.listener.apply(this, ...args);
			});
			this._Events.set(type, this._Events.get(type).filter(item => !item.once));
		}
		return this;
  }
  allOff() {
		this._Events = new Map();
		return this;
  }
}
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

JS用过的几种设计模式:

(1)单例模式

定义:保证一个类仅有一个实例,并提供一个访问它的全局访问点。

实现方法:先判断实例存在与否,如果存在则直接返回,如果不存在就创建了再返回,这就确保了一个类只有一个实例对象。

适用场景:一个单一对象。比如:弹窗,无论点击多少次,弹窗只应该被创建一次。

(2)发布/订阅模式 定义:又叫观察者模式,它定义对象间的一种一对多的依赖关系,当一个对象的状态发生改变时,所有依赖于它的对象都将得到通知。

场景:订阅感兴趣的专栏和公众号。

(3)策略模式 定义:将一个个算法(解决方案)封装在一个个策略类中。

优点:

  • 策略模式可以避免代码中的多重判断条件。
  • 策略模式很好的体现了开放-封闭原则,将一个个算法(解决方案)封装在一个个策略类中。便于切换,理解,扩展。
  • 策略中的各种算法可以重复利用在系统的各个地方,避免复制粘贴。
  • 策略模式在程序中或多或少的增加了策略类。但比堆砌在业务逻辑中要清晰明了。
  • 违反最少知识原则,必须要了解各种策略类,才能更好的在业务中应用。

应用场景:根据不同的员工绩效计算不同的奖金;表单验证中的多种校验规则。

(4)代理模式

定义:为一个对象提供一个代用品或占位符,以便控制对它的访问。

应用场景:图片懒加载(先通过一张loading图占位,然后通过异步的方式加载图片,等图片加载好了再把完成的图片加载到img标签里面。)

(5)中介者模式

定义:通过一个中介者对象,其他所有相关对象都通过该中介者对象来通信,而不是互相引用,当其中的一个对象发生改变时,只要通知中介者对象就可以。可以解除对象与对象之间的紧耦合关系。

应用场景: 例如购物车需求,存在商品选择表单、颜色选择表单、购买数量表单等等,都会触发change事件,那么可以通过中介者来转发处理这些事件,实现各个事件间的解耦,仅仅维护中介者对象即可。

(6)装饰者模式

定义:在不改变对象自身的基础上,在程序运行期间给对象动态的添加方法。

应用场景: 有方法维持不变,在原有方法上再挂载其他方法来满足现有需求;函数的解耦,将函数拆分成多个可复用的函数,再将拆分出来的函数挂载到某个函数上,实现相同的效果但增强了复用性。