观察者模式的重要性无容置疑,作为一名前端工程师假如你只学一个设计模式的话,那么毫无疑问应该是观察者模式。
观察者模式:也被称为发布订阅模式(Publish/Subscribe),它定义了一种一对多的关系,让多个观察者对象同时监听某一个主题对象,这个主题对象的状态发生变化时就会通知所有的观察者对象,使得它们能够自动更新自己。
早些时候,我们订阅报纸,订阅牛奶等,只要我们交了钱,每天早上小哥骑着自行车给我们送报纸,牛奶。
我们以一个简单的例子来了解一下观察者模式。
在react的开发过程中,我们经常会使用redux,那么redux中就使用了观察者模式。(没有使用过也不要紧,我们简单实现一个redux)
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 1function createStore (store) {
2 let curState, listnerCallBack = [];
3
4 function getState() {
5 return curState;
6 }
7 function subscribe(cb) {//订阅,将回调函数存入
8 listnerCallBack.push(cb);
9 }
10 function dispatch(action) {//当有action发送时,那么就回触发已经订阅的所有函数
11 curState = store(curState, action);
12 listnerCallBack.map( v => v() );
13 }
14 dispatch({ type: "@cyl-redux" });
15 return { getState, subscribe, dispatch}
16}
17
18const { subscribe, dispatch } = createStore(()=>{});
19subscribe(function () {
20 console.log("hello");
21})
22subscribe(function () {
23 console.log("CYl");
24})
25
26dispatch({});
27
createStore里面的代码非常简单,我们重点看subscribe和dispatch方法。
subscribe方法是往监听回调函数里面添加方法,当执行dispatch时执行所有注册的回调函数。
dom事件监听也是一个观察者模式。
1
2
3
4
5
6
7
8
9
10
11 1const h1 = document.getElementById("title");
2h1.addEventListener("click", function (){
3 console.log("1");
4})
5h1.addEventListener("click", function (){
6 console.log("2");
7})
8h1.addEventListener("click", function (){
9 console.log("3");
10})
11
当我们点击h1时,那么就会调用这个三个方法
下个例子就是nodeJS里面的event事件对象
1
2
3
4
5
6
7
8
9
10
11 1const { EventEmitter } = require("events");
2
3class Demo extends EventEmitter {
4
5}
6const d = new Demo();
7d.on("data", function () {
8 console.log("cyl")
9})
10d.emit("data");
11
使用on监听 使用emit发布。
我们现在使用原生JS来模拟一下EventEmitter里面的方法,看一下观察者模式的应用
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 1function EventEmitter () {
2 this.event = {};
3}
4//给类型type增加回调函数 callBack
5EventEmitter.prototype.addListener = EventEmitter.prototype.on = function (type, callBack) {
6 if (this.event[type]) { //如果之前存在这个类型的事件,就继续添加
7 this.event[type].push(callBack);
8 } else {
9 this.event[type] = [callBack]; //如果不存在,那么就新生成一个数组
10 }
11}
12//调用类型type的所有的回调函数 callBack
13EventEmitter.prototype.emit = function (type, ...res) { //参数是从第二个开始
14 if (this.event[type]) { //如果存在这个类型的回调函数,那么就执行
15 this.event[type].forEach( (listener) => {
16 listener.apply(this, res); //调用函数
17 })
18 }
19}
20//给类型type增加回调函数 callBack 但是只调用一次
21EventEmitter.prototype.once = function (type, listener) {
22 function wrap(...res) {//接受参数的
23 listener.apply(this, res); //执行 然后立马销毁
24 this.removeListener(type, wrap);
25 }
26 this.on(type, wrap);
27}
28//移除类型为type,的listener
29EventEmitter.prototype.removeListener = function (type, listener) {
30 if (this.event[type]) {
31 this.event[type] = this.event[type].filter( (item) => item!==listener) //true保留
32 }
33}
34
35//移除类型为type,所有的listner
36EventEmitter.prototype.removeAllListener = function (type) {
37 if (this.event[type]) {
38 this.event[type] = [];
39 }
40}
41
42EventEmitter.prototype.listeners = function (type) {
43 return this.event[type];
44}
45
还有比如NodeJS的流,也使用观察者模式,使用过nodeJS的应该马上就可以想起来了。
最后我们使用观察者模式来实现数据绑定。
熟悉react或者vue的前端工程师都知道,react和vue不需要我们关注视图里面的数据更新,当store里面的数据更新时,那么视图就会自动刷新。
现在我们就使用原生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 1const obj = { name: "cyl" };
2
3
4class Dep {
5 constructor () {
6 this.listenCallBack = [];
7 }
8 add (fn) {
9 this.listenCallBack.push(fn);
10 }
11 notify () {
12 this.listenCallBack.map( (v) => { v.update(); } );
13 }
14}
15Dep.target = null;
16
17class Watcher {
18 constructor(obj, key, callBack) {
19 Dep.target = this;
20 this.obj = obj;
21 this.key = this.key;
22 this.value = obj[key];//触发get 将当前的this也就是watcher保存到listenCallBack
23 this.called = callBack;
24 Dep.target = null;
25 }
26 update () {
27 this.value = this.obj[this.key];
28 this.called();
29 }
30}
31function observe(obj) {
32 if (typeof obj === "object" && obj !== null) {
33 for (const key in obj) { //对每个属性进行监听
34 defineObserve(obj, key, obj[key])
35 }
36 }
37}
38
39function defineObserve(obj, key, value) {
40 observe(obj[key]);
41 const dep = new Dep();
42 Object.defineProperty(obj, key, {
43 enumerable: true,
44 configurable: true,
45 get: () => {
46 if (Dep.target) {
47 dep.add(Dep.target); //添加当前的watcher
48 }
49 return value;
50 },
51 set: (newValue) => {
52 value = newValue;
53 dep.notify(); //当被重新设置的时候 那么就调用回调函数
54 }
55 })
56}
57
58observe(obj);
59
60const h1 = document.getElementById("title");
61
62new Watcher(obj, "name", () => {
63
64 h1.innerText = obj.name;
65})
66
67h1.innerText = obj.name;
68setTimeout(() => {
69 obj.name = 6;
70}, 1000)
71
觉得自己能力不错,可以去理解上面代码,我写了一些关键注释
我们可以看到一开始页面的显示的是cyl
1秒之后 变成了 6
观察者的使用场合就是:当一个对象的改变需要同时改变其它对象,并且它不知道具体有多少对象需要改变的时候,就应该考虑使用观察者模式。
这是一个很重要的设计模式,在前端JS中非常广泛。