简单理解观察者模式(pub/sub)在前端中的应用

凌燕 2019-06-28

概念

观察者模式被广泛地应用于JavaScript客户端编程中。所有的浏览器事件(mouseover,keypress等)都是使用观察者模式的例子。这种模式的另一个名字叫“自定义事件”,意思是这些事件是被编写出来的,和浏览器触发的事件相对。它还有另外一个名字叫“订阅者/发布者”模式。
使用这个模式的最主要目的就是促进代码触解耦。在观察者模式中,一个对象订阅另一个对象的指定活动并得到通知,而不是调用另一个对象的方法。订阅者也被叫作观察者,被观察的对象叫作发布者或者被观察者(译注:subject,不知道如何翻译,第一次的时候译为“主体”,第二次译时觉得不妥,还是直接叫被观察者好了)。当一个特定的事件发生的时候,发布者会通知(调用)所有的订阅者,同时还可能以事件对象的形式传递一些消息。 (摘自javascript 模式一书)

维基百科定义:在软件架构中,发布-订阅是一种消息范式,消息的发送者(称为发布者)不会将消息直接发送给特定的接收者(称为订阅者)。而是将发布的消息分为不同的类别,无需了解哪些订阅者(如果有的话)可能存在。同样的,订阅者可以表达对一个或多个类别的兴趣,只接收感兴趣的消息,无需了解哪些发布者(如果有的话)存在。

理解观察者模式:

摘自 http://www.cnblogs.com/tugenh...

JS传统事件就是一个观察者模式,之所以要有观察者模式,是因为有时候和传统事件无关的事件,比如:2个或者更多模块的直接通信问题,比如说我有个index.html页面,我有很多JS文件,比如:

a.js: function a(){}; b.js: function b(){}; c.js function c(){}; 等等。后面还有许多这样的JS,那么我要在index.html初始化这些函数的话,我需要这样调用a();b();c()等等,也就是说页面调用的时候 我要这样调用,增加了依赖性,我要知道有多少个函数要这样初始化调用,但是如果我们现在用观察者模式就不需要知道有哪些订阅者,比如一个模块(或者多个模块)订阅了一个主题(或者事件),另一个模块发布这个主题时候,订阅这个主题模块就可以执行了,观察者主要让订阅者与发布者解耦,发布者不需要知道哪些模块订阅了这个主题,它只管发布这个主题就可以了,同样订阅者也无需知道那个模块会发布这个主题,它只管订阅这个主题就可以了。这样2个模块(或更多模块)就实现了关联了。而不需要和上面代码一样,我要知道哪些模块要初始化,我要怎样初始化。这只是一个简单的列子解释观察者模式要使用在什么地方,我也看过很多博客关于这方面的资料,但是很多人写博客只是讲了如何实现观察者模式及观察者模式的好处,并没有讲我们什么时候该使用观察者模式,所以我列举了上面的列子,就是多个不同业务模块需要相互关联的时候,可以使用观察者模式。就好比requireJS,seaJS,KISSY解决依赖的问题一样(比如A依赖于B,B依赖于C,只要一个解决入口文件,其他都会异步加载出来一样)。也就是说各个模块之间的关联性可以使用观察者模式来设计。

代码实现:在该篇博客(https://www.cnblogs.com/Lucky...

(function(){
    var Event = {
        on: function (type, handler) {
            if (!this.handlers) {
                // this.handlers = {};    
                Object.defineProperty(this, "handlers", {
                    value: {},
                    enumerable: false,  // important 避免拷贝Event时污染handlers
                    configurable: true,
                    writable: true
                })
            }   
            if (typeof this.handlers[type] === 'undefined') {
                this.handlers[type] = [];
            }
            this.handlers[type].push(handler);
        },
        emit: function (eventName) {
            if (this.handlers[arguments[0]] instanceof Array) {
                var handlers = this.handlers[arguments[0]];
                for (var i=0; i<handlers.length; i++) {
                    handlers[i](arguments[1].message);
                }
            }
        },
        unsubscribe: function (type, handler) {
            if (this.handlers[type] instanceof Array) {
                var handlers = this.handlers[type];
                for (var i=0; i<handlers.length; i++) {
                    if (handlers[i] === handler) {
                        break;
                    }
                }
                handlers.splice(i, 1);
            }
        }
    };
  
    Event.on('test', function (message) {
        console.log(message);
    });
    Event.on('test', function () {
        console.log('test');
    });

    Event.emit('test', {message: 'hello world'});

    var person1 = {
        sayHello: function (message) {
            console.log(message);
        }
    };
    var person2 = {
        sayHello: function (message) {
            console.log(message);
        }
    };
    Object.assign(person1, Event);
    Object.assign(person2, Event);
    person1.on('call1', person1.sayHello);
    person2.on('call2', person2.sayHello);
    person1.emit('call1', {message:'person1 is calling call1.'}); // 'person1 is calling call1.' 
    person1.emit('call2', {message:'p111'}); //  no output
    person2.emit('call1', {message:'p222'}); //  no output
    person2.emit('call2', {message:'person2 is calling call2.'}); // 'person2 is calling call2'
    person1.unsubscribe('call1', person1.sayHello);
    person1.emit('call1', {message:'person1 is calling call1 again.'}); // no output
    person1.on('call1', person1.sayHello);
    person1.emit('call1', {message:'person1 is calling call1 again.'}); // person1 is calling call1 again.

    let paper = Object.assign({}, Event);
    let mike = {
        haveABreak: function (news) {
            let msg = 'Mike just read news: ' + news;
            console.log('-------------');
            console.log(msg);
        }
    };
    let tom = {
        haveABreak: function (news) {
            let msg = 'Tom just read news: ' + news;
            console.log(msg);
            console.log('-------------');
        }
    };
    paper.on('dailyNews', mike.haveABreak);
    paper.on('dailyNews', tom.haveABreak);
    paper.emit('dailyNews', {message: 'Coding is getting better.'});

})()

可在我的github上下载上述代码(https://github.com/ylzsmallsu...),并包含JavaScript模式中观察者模式例子。

此外,观察者模式还可用于实现数据绑定

思想:使用数据特性来为 HTML 代码进行绑定,所有被绑定在一起的 JavaScript 对象和 DOM 元素都会订阅一个 PubSub 对象。只要 JavaScript 对象或者一个 HTML 输入元素监听到数据的变化时,就会触发绑定到 PubSub 对象上的事件,从而其他绑定的对象和元素都会做出相应的变化。

参考文章

本篇文章主要是对查阅的几篇文档集中梳理,有兴趣的可以翻阅以上参考文章。

相关推荐