ThinkChat2.0新版上线,更智能更精彩,支持会话、画图、阅读、搜索等,送10W Token,即刻开启你的AI之旅 广告
## 观察者模式 > 观察者模式定义了对象之间的一对多依赖,这 样一来,当一个对象改变状态时,它的所有依赖者都 会收到通知并自动更新。 ![](https://box.kancloud.cn/010fe9c828c8d471b5d4a81100cf0d69_1180x714.png) * 1. 报社的业务就是出版报纸。 * 2. 向某家报社订阅报纸,只要他们有新报纸出版,就会给你送 来。只要你是他们的订户,你就会一直收到新报纸。 * 3. 当你不想再看报纸的时候,取消订阅,他们就不会再送新报 纸来。 * 4. 只要报社还在运营,就会一直有人(或单位)向他们订阅报 纸或取消订阅报纸。 ![](https://box.kancloud.cn/0e714fd2c8f21a64e3e152c6231651ad_3824x2652.png) 观察者模式提供了一种对象设计,让主题和观察者之间松耦合。 关于观察者的一切,主题只知道观察者实现了某个接口(也就是Observer接口)。主 题不需要知道观察者的具体类是谁、做了些什么或其他任何细节。 任何时候我们都可以增加新的观察者。因为主题唯一依赖的东西是一个实现 Observer接口的对象列表,所以我们可以随时增加观察者。事实上,在运行时我们可 以用新的观察者取代现有的观察者,主题不会受到任何影响。同样的,也可以在任何 时候删除某些观察者。 有新类型的观察者出现时,主题的代码不需要修改。假如我们有个新的具体类需要当 观察者,我们不需要为了兼容新类型而修改主题的代码,所有要做的就是在新的类里 实现此观察者接口,然后注册为观察者即可。主题不在乎别的,它只会发送通知给所 有实现了观察者接口的对象。 改变主题或观察者其中一方,并不会影响另一方。因为两者是松耦合的,所以只要他 们之间的接口仍被遵守,我们就可以自由地改变他们。 基本概念介绍 观察者(observer) 模式广泛用于客户端Javascript[编程](http://www.2cto.com/kf)中。所有的[浏览器](http://www.2cto.com/os/liulanqi/)事件都是该模式的例子。它的另一个名字也称为自定义事件(custom events),与那些由浏览器触发的事件相比,自定义事件表示是由你编程实现的事件。此外,该模式的另一个别名也称为订阅/发布(subscriber/publisher)模式。 设计该模式背后的主要动力是促进形成松散耦合。在这种模式中,并不是一个对象调用另一个对象的方法,而是一个对象订阅另一个对象的特定活动并在状态改变后获得通知。订阅者也称为观察者,而补观察的对象称为发布者或主题。当发生了一个重要的事件时,发布者将会通知(调用)所有订阅者并且可能经常以事件对象的形式传递消息。 示例:杂志订阅 假设有一个发布者paper,它每天出版报纸及月刊杂志。订阅者joe将被通知任何时候所发生的新闻。 该paper对象需要一个subscribers(topics )属性,该属性是一个存储所有订阅者的数组。订阅行为只是将其加入到这个数组中。当一个事件发生时,paper将会循环遍历订阅者列表并通知它们。通知意味着调用订阅者对象的某个方法。故当用户订阅信息时,该订阅者需要向paper的subscribe()提供它的其中一个方法。 paper也提供了unsubscribe()方法,该方法表示从订阅者数组(即subscribers属性)中删除订阅者。paper最后一个重要的方法是publish(),它会调用这些订阅者的方法,总而言之,发布者对象paper需要具有以下这些成员: 1.subscribers 一个数组 2.subscribe() 将订阅者添加到subscribers数组中 3.unsubscribe() 从subscribers数组中删除订阅者 4.publish() 循环遍历subscribers数组中的每一个元素,并且调用他们注册时所提供的方法 所有这三种方法都需要一个topic参数,因为发布者可能触发多个事件(比如同时发布一本杂志和一份报纸)而用户可能仅选择订阅其中一种,而不是另外一种。 由于这些成员对于任何发布者对象都是通用的,故将它们作为独立对象的一个部分来实现是很有意义的。那样我们可将其复制到任何对象中,并将任意给定对象变成一个发布者。 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'}]); ~~~ 试试多个订阅者订阅同个主题: ~~~ //来,订阅一个 pubsub.subscribe('example1', function (topics, data) { console.log(topics + ": " + data); }); //来,再订阅一个 pubsub.subscribe('example1', function (topics, data) { console.log(topics + "******* " + data); }); //发布通知 pubsub.publish('example1', 'hello world!'); ~~~