介绍

观察者模式又叫发布订阅模式(Publish/Subscribe),它定义了一种一对多的关系,让多个观察者对象同时监听某一个主题对象,这个主题对象的状态发生变化时就会通知所有的观察者对象,使得它们能够自动更新自己。

使用观察者模式的好处:

  1. 支持简单的广播通信,自动通知所有已经订阅过的对象。
  2. 页面载入后目标对象很容易与观察者存在一种动态关联,增加了灵活性。
  3. 目标对象与观察者之间的抽象耦合关系能够单独扩展以及重用。

正文(版本一)

JS里对观察者模式的实现是通过回调来实现的,我们来先定义一个pubsub对象,其内部包含了3个方法:订阅、退订、发布。

var pubsub = {};(function (q) {

var topics = {}, // 回调函数存放的数组        subUid = -1;// 发布方法    q.publish = function (topic, args) {

if (!topics[topic]) {return false;        }

        setTimeout(function () {var subscribers = topics[topic],                len = subscribers ? subscribers.length : 0;

while (len--) {                subscribers[len].func(topic, args);            }        }, 0);

return true;

    };//订阅方法    q.subscribe = function (topic, func) {

if (!topics[topic]) {            topics[topic] = [];        }

var token = (++subUid).toString();        topics[topic].push({            token: token,            func: func        });return token;    };//退订方法    q.unsubscribe = function (token) {for (var m in topics) {if (topics[m]) {for (var i = 0, j = topics[m].length; i < j; i++) {if (topics[m][i].token === token) {                        topics[m].splice(i, 1);return token;                    }                }            }        }return false;    };} (pubsub));

使用方式如下:

//来,订阅一个pubsub.subscribe('example1', function (topics, data) {    console.log(topics + ": " + data);});

//发布通知pubsub.publish('example1', 'hello world!');pubsub.publish('example1', ['test', 'a', 'b', 'c']);pubsub.publish('example1', [{ 'color': 'blue' }, { 'text': 'hello'}]);

怎么样?用起来是不是很爽?但是这种方式有个问题,就是没办法退订订阅,要退订的话必须指定退订的名称,所以我们再来一个版本:

//将订阅赋值给一个变量,以便退订var testSubscription = pubsub.subscribe('example1', function (topics, data) {    console.log(topics + ": " + data);});

//发布通知pubsub.publish('example1', 'hello world!');pubsub.publish('example1', ['test', 'a', 'b', 'c']);pubsub.publish('example1', [{ 'color': 'blue' }, { 'text': 'hello'}]);

//退订setTimeout(function () {    pubsub.unsubscribe(testSubscription);}, 0);

//再发布一次,验证一下是否还能够输出信息pubsub.publish('example1', 'hello again! (this will fail)');

版本二

我们也可以利用原型的特性实现一个观察者模式,代码如下:

function Observer() {this.fns = [];}Observer.prototype = {    subscribe: function (fn) {this.fns.push(fn);    },    unsubscribe: function (fn) {this.fns = this.fns.filter(function (el) {if (el !== fn) {return el;                            }                        }                    );    },    update: function (o, thisObj) {var scope = thisObj || window;this.fns.forEach(function (el) {                            el.call(scope, o);                        }                    );    }};

//测试var o = new Observer;var f1 = function (data) {    console.log('Robbin: ' + data + ', 赶紧干活了!');};

var f2 = function (data) {    console.log('Randall: ' + data + ', 找他加点工资去!');};

o.subscribe(f1);o.subscribe(f2);

o.update("Tom回来了!")

//退订f1o.unsubscribe(f1);//再来验证o.update("Tom回来了!");   

如果提示找不到filter或者forEach函数,可能是因为你的浏览器还不够新,暂时不支持新标准的函数,你可以使用如下方式自己定义:

if (!Array.prototype.forEach) {    Array.prototype.forEach = function (fn, thisObj) {var scope = thisObj || window;for (var i = 0, j = this.length; i < j; ++i) {            fn.call(scope, this[i], i, this);        }    };}if (!Array.prototype.filter) {    Array.prototype.filter = function (fn, thisObj) {var scope = thisObj || window;var a = [];for (var i = 0, j = this.length; i < j; ++i) {if (!fn.call(scope, this[i], i, this)) {continue;            }            a.push(this[i]);        }return a;    };}

版本三

如果想让多个对象都具有观察者发布订阅的功能,我们可以定义一个通用的函数,然后将该函数的功能应用到需要观察者功能的对象上,代码如下:

//通用代码var observer = {//订阅    addSubscriber: function (callback) {this.subscribers[this.subscribers.length] = callback;    },//退订    removeSubscriber: function (callback) {for (var i = 0; i < this.subscribers.length; i++) {if (this.subscribers[i] === callback) {delete (this.subscribers[i]);            }        }    },//发布    publish: function (what) {for (var i = 0; i < this.subscribers.length; i++) {if (typeof this.subscribers[i] === 'function') {this.subscribers[i](what);            }        }    },// 将对象o具有观察者功能    make: function (o) { for (var i in this) {            o[i] = this[i];            o.subscribers = [];        }    }};

然后订阅2个对象blogger和user,使用observer.make方法将这2个对象具有观察者功能,代码如下:

var blogger = {    recommend: function (id) {var msg = 'dudu 推荐了的帖子:' + id;this.publish(msg);    }};

var user = {    vote: function (id) {var msg = '有人投票了!ID=' + id;this.publish(msg);    }};

observer.make(blogger);observer.make(user);

使用方法就比较简单了,订阅不同的回调函数,以便可以注册到不同的观察者对象里(也可以同时注册到多个观察者对象里):

var tom = {    read: function (what) {        console.log('Tom看到了如下信息:' + what)    }};

var mm = {    show: function (what) {        console.log('mm看到了如下信息:' + what)    }};// 订阅blogger.addSubscriber(tom.read);blogger.addSubscriber(mm.show);blogger.recommend(123); //调用发布

//退订blogger.removeSubscriber(mm.show);blogger.recommend(456); //调用发布

//另外一个对象的订阅user.addSubscriber(mm.show);user.vote(789); //调用发布

jQuery版本

根据jQuery1.7版新增的on/off功能,我们也可以定义jQuery版的观察者:

(function ($) {

var o = $({});

    $.subscribe = function () {        o.on.apply(o, arguments);    };

    $.unsubscribe = function () {        o.off.apply(o, arguments);    };

    $.publish = function () {        o.trigger.apply(o, arguments);    };

} (jQuery));

调用方法比上面3个版本都简单:

//回调函数function handle(e, a, b, c) {// `e`是事件对象,不需要关注    console.log(a + b + c);};

//订阅$.subscribe("/some/topic", handle);//发布$.publish("/some/topic", ["a", "b", "c"]); // 输出abc

$.unsubscribe("/some/topic", handle); // 退订

//订阅$.subscribe("/some/topic", function (e, a, b, c) {    console.log(a + b + c);});

$.publish("/some/topic", ["a", "b", "c"]); // 输出abc

//退订(退订使用的是/some/topic名称,而不是回调函数哦,和版本一的例子不一样$.unsubscribe("/some/topic"); 

可以看到,他的订阅和退订使用的是字符串名称,而不是回调函数名称,所以即便传入的是匿名函数,我们也是可以退订的。

总结

观察者的使用场合就是:当一个对象的改变需要同时改变其它对象,并且它不知道具体有多少对象需要改变的时候,就应该考虑使用观察者模式。

总的来说,观察者模式所做的工作就是在解耦,让耦合的双方都依赖于抽象,而不是依赖于具体。从而使得各自的变化都不会影响到另一边的变化。

参考地址:

https://github.com/shichuan/javascript-patterns/blob/master/design-patterns/observer.html

http://www.addyosmani.com/resources/essentialjsdesignpatterns/book/#observerpatternjavascript

https://gist.github.com/661855

同步与推荐

本文已同步至目录索引:深入理解JavaScript系列

深入理解JavaScript系列文章,包括了原创,翻译,转载等各类型的文章,如果对你有用,请推荐支持一把,给大叔写作的动力。

转载于:https://www.cnblogs.com/TomXu/archive/2012/03/02/2355128.html

深入理解JavaScript系列(32):设计模式之观察者模式相关推荐

  1. 深入理解JavaScript系列(33):设计模式之策略模式(转)

    介绍 策略模式定义了算法家族,分别封装起来,让他们之间可以互相替换,此模式让算法的变化不会影响到使用算法的客户. 正文 在理解策略模式之前,我们先来一个例子,一般情况下,如果我们要做数据合法性验证,很 ...

  2. 深入理解JavaScript系列(27):设计模式之建造者模式

    介绍 在软件系统中,有时候面临着"一个复杂对象"的创建工作,其通常由各个部分的子对象用一定的算法构成:由于需求的变化,这个复杂对象的各个部分经常面临着剧烈的变化,但是将它们组合在一 ...

  3. 深入理解JavaScript系列(33):设计模式之策略模式

    介绍 策略模式定义了算法家族,分别封装起来,让他们之间可以互相替换,此模式让算法的变化不会影响到使用算法的客户. 正文 在理解策略模式之前,我们先来一个例子,一般情况下,如果我们要做数据合法性验证,很 ...

  4. 深入理解JavaScript系列——汤姆大叔

    原文地址 深入理解JavaScript系列文章,包括了原创,翻译,转载,整理等各类型文章,如果对你有用,请推荐支持一把,给大叔写作的动力. 深入理解JavaScript系列(1):编写高质量JavaS ...

  5. 汤姆大叔深入理解JavaScript系列

    深入理解JavaScript系列文章,包括了原创,翻译,转载,整理等各类型文章,如果对你有用,请推荐支持一把,给大叔写作的动力. 深入理解JavaScript系列(1):编写高质量JavaScript ...

  6. [转载]深入理解JavaScript系列 --汤姆大叔

    深入理解JavaScript系列文章,包括了原创,翻译,转载,整理等各类型文章,如果对你有用,请推荐支持一把,给大叔写作的动力. 深入理解JavaScript系列(1):编写高质量JavaScript ...

  7. 深入理解JavaScript系列(转自汤姆大叔)

    深入理解JavaScript系列(转自汤姆大叔) 深入理解JavaScript系列文章,包括了原创,翻译,转载,整理等各类型文章,如果对你有用,请推荐支持一把,给大叔写作的动力. 深入理解JavaSc ...

  8. 深入理解JavaScript系列:《你真懂JavaScript吗?》答案详解

    介绍 昨天发的<大叔手记(19):你真懂JavaScript吗?>里面的5个题目,有很多回答,发现强人还是很多的,很多人都全部答对了. 今天我们来对这5个题目详细分析一下,希望对大家有所帮 ...

  9. 深入理解JavaScript系列(21):S.O.L.I.D五大原则之接口隔离原则ISP

    前言 本章我们要讲解的是S.O.L.I.D五大原则JavaScript语言实现的第4篇,接口隔离原则ISP(The Interface Segregation Principle). 英文原文:htt ...

最新文章

  1. 图像均值迁移模糊:pyrMeanShiftFiltering()
  2. 在.NET 3.5 平台上使用LINQ to SQL创建三层/多层Web应用系统(Part2) 转
  3. 百家中华老字号故宫过大年
  4. 【项目管理】RUP内容整理
  5. MySQL5.6免安装配置与“系统找不到指定的文件”错误
  6. springboot+mybatis日志显示SQL
  7. php验证码图片看不清更换代码,php如何实现验证码看不清换一张的效果
  8. C# Stream 和 byte[] 之间的转换[转]
  9. BZOJ2434 [Noi2011]阿狸的打字机(AC自动机 + fail树 + DFS序 + 线段树)
  10. 手柄游戏之《恶魔城暗影之王》推荐
  11. libvlc获取一帧_用VLC 实现获取播放每帧视频的时间(UTC)
  12. win7计算机出现空白图标,win7任务栏右下角图标显示为空白如何解决_win7任务栏右下角图标空白怎么去掉...
  13. java gc roots_Java 虚拟机枚举 GC Roots 解析
  14. Dynamics 365 安装Report Authoring Extension时,提示重新启动的问题
  15. React中CodeMirror插件的使用及封装
  16. JAVA 基因牛的繁殖
  17. 在DX12中使用imgui 入门教程 立方体旋转+改变背景颜色
  18. Mathematica9.0中文版软件下载详细图文安装步骤!
  19. 2019011工作日志-关于代币空投合约的编写和js基于koa框架的整合
  20. js 四句话搞懂原型和原型链

热门文章

  1. 计算机控制cs,计算机控制
  2. scanf在c语言中的作用是什么?
  3. 26条C++的经典语录,哪几句戳中你的心!
  4. python除法函数一般如何命名_关于python:当用于除法时,’/’和’//’之间有什么区别?...
  5. matlab 图像 幅度谱 低通滤波_数字图像处理期末复习2018-12-21
  6. python数据写入表格生成图片_python带表格图片生成封装
  7. centos7 mysql启动后端口_centos7 修改mysql5.7默认端口后启动异常
  8. 中兴c600olt数据配置_中兴天机Axon 10 Pro再掀波澜,4G版3199元起,5G版还有悬念?...
  9. 飞畅科技-工业以太网交换机的差异性
  10. 光纤收发器有什么用?光纤收发器的作用是什么?