Skip to main content
 首页 » 编程设计

javascript观察者模式

2022年07月16日158powertoolsteam
   1 <!DOCTYPE html> 
   2 <html> 
   3 <head> 
   4     <title>观察者模式</title> 
   5     <meta charset="utf-8"> 
   6 </head> 
   7 <body> 
   8 <script> 
   9     function extend(subclass, superclass) { 
  10         var F = function () { 
  11         }; 
  12         F.prototype = superclass.prototype; 
  13         subclass.prototype = new F(); 
  14         subclass.prototype.constructor = subclass; 
  15         subclass.super = superclass.prototype; 
  16  
  17  
  18         if (superclass.prototype.constructor === Object.prototype.constructor) { 
  19             superclass.prototype.constructor = superclass; 
  20         } 
  21     } 
  22 </script> 
  23 <script> 
  24 /** 
  25  * 观察者模式 
  26  * 
  27  * 定义: 
  28  * 定义对象间的一种一对多的依赖关系。当一个对象的状态发生改变时,所有依赖于它的对象都得到通知并被自动更新。 
  29  * 
  30  * 本质: 
  31  * 触发联动 
  32  * 
  33  * 本质: 
  34  * 当修改目标对象的状态的时候,就会触发相应的通知,然后会循环调用所有注册的观察者对象的相应方法,其实就相当于联动调用这些观察者的方法。 
  35  * 而且这个联动还是动态的,可以通过注册和取消注册来控制观察者,因而可以在程序运行期间,通过动态地控制观察者,来变相地实现添加和删除某些功能处理,这些功能就是观察者在update的时候执行的功能。 
  36  * 同时目标对象和观察者对象的解耦,又保证了无论观察者发生怎样的变化,目标对象总是能够正确地联动起来。 
  37  * 
  38  * 
  39  * 在事件驱动的环境中。比如浏览器这种持续寻求用户关注的环境中,观察者模式(又名发布者-订阅者(publisher-subscriber)模式)是一种管理人与其人物之间的关系(确却的讲,是对象及其行为和状态之间的关系)的得力工具。这种模式的实质就是你可以对程序中某个对象的状态进行观察,并且在其发生改变时能够得到通知。 
  40  * 观察者模式中存在两个角色:观察者和被观察者,又叫发布者和订阅者。 
  41  * 
  42  * 认识观察者 
  43  * 
  44  * 1.目标和观察者之间的关系 
  45  * 按照模式的定义,目标和观察者之间是典型的一对多的关系。 
  46  * 但是要注意,如果观察者只有一个,也是可以的,这样就变相实现了目标和观察者之间一对一的关系,这也使得在处理一个对象的状态变化会影响到另一个对象的时候,也可以考虑使用观察者模式。 
  47  * 同样地,一个观察者也可以观察多个目标,如果观察者为多个目标定义的通知更新方法都是update方法的话,这会带来麻烦,因为需要接受多个目标的通知,如果一个update方法,那就需要在方法内部区分,到底这个更新的通知来自于哪个目标,不同的目标有不同的后续操作。 
  48  * 一般情况下,观察者应该为不同的观察者目标定义不同的回调方法,这样实现最简单,不需要在update方法内部进行区分。 
  49  * 
  50  * 2.单向依赖 
  51  * 在观察者模式中,观察者和目标是单向依赖的,只有观察者依赖于目标,而目标是不会依赖于观察者的。 
  52  * 它们之间联系的主动权掌握在目标手中,只有目标知道什么时候需要通知观察者,在整个过程中,观察者始终是被动的,被动地等待目标的通知,等待目标传值给它。 
  53  * 对目标而言,所有的观察者都是一样的,比如某些状态变化,只需要通知部分观察者,但那是属于稍微变形的用法了,不属于标准的,原始的观察者模式了。 
  54  * 
  55  * 3.基本的实现说明 
  56  * 1)具体的目标实现对象要能维护观察者的注册信息,最简单的实现方案就如同下面的例子,采用一个集合来保存观察者的注册信息。 
  57  * 2)具体的目标实现对象需要维护引起通知的状态,一般情况下是目标自身的状态,变形使用情况下,也可以识别的对象的状态。 
  58  * 3)具体的观察者实现对象需要能接受目标的通知,能够接受目标传递的数据,或者是能够主动去获取目标的数据,并进行后续处理。 
  59  * 4)如果是一个观察者观察多个目标,那么在观察者的更新方法里面,需要去判断是来自哪一个目标。一种简单的解决方案就是扩展update方法,比如在方法里面多传递一个参数进行区分等;还有一种更简单的方法,那就是干脆定义不同的回调方法。 
  60  * 
  61  * 4.命名建议 
  62  * 1)观察者模式有被称为发布订阅模式 
  63  * 2)目标接口的定义,建议在名称后面跟Subject 
  64  * 3)观察者接口的定义,建议在后面跟Observer 
  65  * 4)观察者接口的更新方法,建议名称为uodate,当然方法的参数可以根据需要定义,参数个数不限,参数类型不限。 
  66  * 
  67  * 5.触发时机 
  68  * 在实现观察者模式的时候,一定要注意触发通知的时机。一般情况下,是在完成了状态维护后触发,因为通知会传递数据,不能够先通知后改数据,这很容易出问题,会导致观察者和目标对象的状态不一致。 
  69  * 
  70  * 6.相互观察 
  71  * 在某些应用中,可能会出现目标和观察者相互观察的情况。比如有两套观察者模式的应用,其中一套观察者模式的实现是A对象,B对象观察C对象;在另一套观察者模式的实现里面,实现的是B对象,C对象观察A对象,那么A对象和C对象就是在相互观察, 
  72  * A对象的状态便会引起C对象的联动操作,反过来,C对象的状态变化也会引起A对象的联动操作。对于出现这种状况,要特别小心处理,因为可能会出现死循环的情况。 
  73  * 
  74  * 7.观察者模式的调用顺序 
  75  * 一,准备阶段(维护目标和观察者关系的阶段) 
  76  * 1.创建目标对象 
  77  * 2.创建观察者 
  78  * 3.向目标对象注册观察者对象 
  79  * 
  80  * 二,运行阶段 
  81  * 1.改变目标对象的状态 
  82  * 2.通知所有注册的观察者对象进行相应的处理 
  83  * 3.回调目标对象,获取相应的数据 
  84  * 
  85  * 8.通知的顺序 
  86  * 从理论上来说,当目标对象的状态变化后通知所有观察者的时候,顺序是不确定的,因此观察者实现的功能,绝对不要依赖于通知的顺序。也就是说,多个观察者之间的功能是平行的,相互不应该有先后的依赖关系。 
  87  * 
  88  * 
  89  * 何时使用 
  90  * 1.当一个抽象模型有两个方面,其中一个方面的操作依赖于另一个方面的状态变化,那么就可以选用观察者模式,将这两者封装成观察者和目标对象,当目标对象变化的时候,依赖于它的观察者对象也会发生相应的变化。这样就把抽象模型的这两个方面分离开了,使得他们可以独立地改变和复用。 
  91  * 2.如果在更改一个对象的时候,需要同时连带改变其他的对象,而且不知道究竟应该有多少对象需要被连带改变,这种情况可以选用观察者模式,被更改的哪一个对象很明显就相当于是目标对象,而需要连带修改的多个其他对象,就作为多个观察者对象了。 
  92  * 3.当一个对象必须通知其他对象,但是你又希望这个对象和其他被它统治的对象是松散耦合的。这个对象相当于是目标对象,而被它通知的对象就是观察者对象了。 
  93  */ 
  94  
  95 (function () { 
  96     // 示例代码 
  97  
  98     /** 
  99      * 目标对象,它知道观察它的观察者,并提供注册和删除观察者的接口 
 100      */ 
 101     var Subject = function () { 
 102         // 用来保存注册的观察者对象 
 103         this.observers = []; 
 104     }; 
 105     Subject.prototype = { 
 106         // 注册观察者对象 
 107         attach: function (observer) { 
 108             this.observers.push(observer); 
 109         }, 
 110         // 删除观察者对象 
 111         detach: function (observer) { 
 112             for (var i = 0, len = this.observers.length; i < len; i++) { 
 113                 if (observer = this.observers[i]) { 
 114                     this.observers.splice(i, 1); 
 115                     return true; 
 116                 } 
 117             } 
 118  
 119             return false; 
 120         }, 
 121         // 通知所有注册的观察者对象 
 122         notifyObservers: function () { 
 123             for (var i = 0, len = this.observers.length; i < len; i++) { 
 124                 this.observers[i].update(this); 
 125             } 
 126         } 
 127     }; 
 128  
 129     /** 
 130      * 具体的目标对象,份额则把有关状态存入到相应的观察者对象 
 131      * 并在自己状态发生改变时,通知各个观察者 
 132      */ 
 133     var ConcreteSubject = function () { 
 134         ConcreteSubject.super.constructor.call(this); 
 135         // 目标对象状态 
 136         this.subjectState = ''; 
 137     }; 
 138     extend(ConcreteSubject, Subject); 
 139     ConcreteSubject.prototype.getSubjectState = function () { 
 140         return this.subjectState; 
 141     }; 
 142     ConcreteSubject.prototype.setSubjectState = function (subjectState) { 
 143         this.subjectState = subjectState; 
 144         // 状态发生改变,通知各个观察者 
 145         this.notifyObservers(); 
 146     }; 
 147  
 148     // 观察者接口,定义一个更新的接口给那些在目标发生改变的时候被通知的对象 
 149     var Observer = function () { 
 150     }; 
 151     Observer.prototype.update = function () { 
 152     }; 
 153  
 154     var ConcreteObserver = function () { 
 155         this.observerState = ''; 
 156     }; 
 157     extend(ConcreteObserver, Observer); 
 158     ConcreteObserver.prototype.update = function (subject) { 
 159         this.observerState = subject.getSubjectState(); 
 160     }; 
 161 }()); 
 162  
 163 (function () { 
 164     // 订阅报纸,拉模型 
 165  
 166     // 目标对象,作为被观察者,报社 
 167     function Subject() { 
 168         this.readers = []; 
 169     } 
 170  
 171     Subject.prototype = { 
 172         // 报纸的读者需要向报社订阅,先要注册 
 173         attach: function (reader) { 
 174             this.readers.push(reader); 
 175         }, 
 176         detach: function (reader) { 
 177             for (var i = 0, len = this.readers.length; i < len; i++) { 
 178                 if (reader === this.readers[i]) { 
 179                     this.readers.splice(i, 1); 
 180                     return; 
 181                 } 
 182             } 
 183         }, 
 184         // 当每期报纸印刷出来后,就要迅速主动地被送到读者的手中 
 185         // 相当于通知读者,让他们知道 
 186         notifyObservers: function () { 
 187             for (var i = 0, len = this.readers.length; i < len; i++) { 
 188                 this.readers[i].update(this); 
 189             } 
 190         } 
 191     }; 
 192  
 193     // 报纸对象,具体的目标实现 
 194     function NewsPaper() { 
 195         NewsPaper.super.constructor.call(this); 
 196         this.content = ''; 
 197     } 
 198  
 199     extend(NewsPaper, Subject); 
 200     // 获取报纸的具体内容 
 201     NewsPaper.prototype.getContent = function () { 
 202         return this.content; 
 203     }; 
 204     // 设置报纸的具体内容,相当于要出版报纸了 
 205     NewsPaper.prototype.setContent = function (content) { 
 206         this.content = content; 
 207         // 内容有了,说明又出报纸了,那就通知所有的读者 
 208         this.notifyObservers(); 
 209     }; 
 210  
 211     // 观察者接口 
 212     function Observer() { 
 213     } 
 214  
 215     Observer.prototype.update = function () { 
 216     }; 
 217  
 218  
 219     function Reader() { 
 220         this.name = ''; 
 221     } 
 222  
 223     extend(Reader, Observer); 
 224     Reader.prototype.update = function (subject) { 
 225         console.log(this.name + '收到报纸了,阅读它,内容是:' + subject.getContent()); 
 226     }; 
 227  
 228     new function () { 
 229         var subject = new NewsPaper(); 
 230  
 231         var reader1 = new Reader(); 
 232         reader1.name = '张三'; 
 233  
 234         var reader2 = new Reader(); 
 235         reader2.name = '李四'; 
 236  
 237         var reader3 = new Reader(); 
 238         reader3.name = '王五'; 
 239  
 240         subject.attach(reader1); 
 241         subject.attach(reader2); 
 242         subject.attach(reader3); 
 243  
 244         subject.setContent('本期内容是观察者模式'); 
 245     }(); 
 246 }()); 
 247  
 248 (function () { 
 249     /** 
 250      * 推模型和拉模型 
 251      * 
 252      * 推模型 
 253      * 目标对象主动向观察者推送目标的详细信息,不管观察者是否需要,推送的信息通常是目标对象的全部或部分数据,相当于广播通信。 
 254      * 
 255      * 拉模型 
 256      * 目标对象在通知观察者的时候,值传递少量信息。如果观察者需要更具体的信息,有观察者主动到目标对象中获取,相当于是观察者从目标对象中拉数据。一般这种模式的实现中,会把目标对象自身通过update方法传递给观察者,这样在观察者需要获取数据的时候,就可以通过这个引用来获取了。 
 257      */ 
 258  
 259     /* 
 260      前面的例子就是典型的拉模型,下面看看推模型的实现 
 261      */ 
 262     // 订阅报纸,推模型 
 263  
 264     // 目标对象,作为被观察者,报社 
 265     function Subject() { 
 266         this.readers = []; 
 267     } 
 268  
 269     Subject.prototype = { 
 270         // 报纸的读者需要向报社订阅,先要注册 
 271         attach: function (reader) { 
 272             this.readers.push(reader); 
 273         }, 
 274         detach: function (reader) { 
 275             for (var i = 0, len = this.readers.length; i < len; i++) { 
 276                 if (reader === this.readers[i]) { 
 277                     this.readers.splice(i, 1); 
 278                     return; 
 279                 } 
 280             } 
 281         }, 
 282         /* 
 283          推模型的目标对象 
 284          跟拉模型的不同之处 
 285          1.通知所有观察者的方法,以前是没有参数的,现在需要传入需要主动推送的数据。 
 286          2.在循环通知观察者的时候,也就是循环调用观察者update方法的时候,传入的参数不同了 
 287          */ 
 288         // 当每期报纸印刷出来后,就要迅速主动地被送到读者的手中 
 289         // 相当于通知读者,让他们知道 
 290         /*------------------------------------*/ 
 291         notifyObservers: function (content) { 
 292             for (var i = 0, len = this.readers.length; i < len; i++) { 
 293                 this.readers[i].update(content); 
 294             } 
 295         } 
 296         /*------------------------------------*/ 
 297     }; 
 298  
 299     // 报纸对象,具体的目标实现 
 300     function NewsPaper() { 
 301         NewsPaper.super.constructor.call(this); 
 302         this.content = ''; 
 303     } 
 304  
 305     extend(NewsPaper, Subject); 
 306     // 获取报纸的具体内容 
 307     NewsPaper.prototype.getContent = function () { 
 308         return this.content; 
 309     }; 
 310     // 设置报纸的具体内容,相当于要出版报纸了 
 311     /*------------------------------------*/ 
 312     NewsPaper.prototype.setContent = function (content) { 
 313         this.content = content; 
 314         // 内容有了,说明又出报纸了,那就通知所有的读者 
 315         this.notifyObservers(content); 
 316     }; 
 317     /*------------------------------------*/ 
 318  
 319     // 观察者接口 
 320     function Observer() { 
 321     } 
 322  
 323     Observer.prototype.update = function () { 
 324     }; 
 325  
 326  
 327     function Reader() { 
 328         this.name = ''; 
 329     } 
 330  
 331     extend(Reader, Observer); 
 332     // 推模型通常都是把需要传递的数据直接推送给观察者对象, 
 333     // 所以观察者接口中的update方法的参数需要变化。 
 334     /*------------------------------------*/ 
 335     Reader.prototype.update = function (content) { 
 336         // 以前需要到目标对象中获取自己需要的数据,现在是直接接受传入的数据 
 337         console.log(this.name + '收到报纸了,阅读它,内容是:' + content); 
 338     }; 
 339     /*------------------------------------*/ 
 340  
 341     new function () { 
 342         var subject = new NewsPaper(); 
 343  
 344         var reader1 = new Reader(); 
 345         reader1.name = '张三'; 
 346  
 347         var reader2 = new Reader(); 
 348         reader2.name = '李四'; 
 349  
 350         var reader3 = new Reader(); 
 351         reader3.name = '王五'; 
 352  
 353         subject.attach(reader1); 
 354         subject.attach(reader2); 
 355         subject.attach(reader3); 
 356  
 357         subject.setContent('本期内容是观察者模式'); 
 358     }(); 
 359  
 360     /* 
 361      应该使用哪种模型 
 362    
 363      1.推模型是假定目标对象知道观察者需要的数据;而拉模型是目标对象不知道观察者具体需要什么数据,没有办法的情况下,干脆把自身传给观察者,让观察者自己按需取值。 
 364      2.推模型可能会使得观察者对象难以复用,因为观察者定义的update方法是按需而定义的,可能无法兼顾所有情况。这意味着出现新情况的时候,就可能需要提供新的update方法,或者干脆重新实现观察者。 
 365      而拉模型就不会造成这样的情况,因为拉模型下,update方法的参数就、是目标对象本身,这基本上可以适应各种情况的需要。 
 366      */ 
 367 }()); 
 368  
 369  
 370 /* 
 371  示例:报纸的投送(JS pro design pattern) 
 372  
 373  在报纸行业中,发行和订阅的顺序进行有赖于一些关键性的角色和行为。首先是读者,他们都是订阅者(subscriber),他们消费数据并且根据读到的消息做出反应。。另一个角色是发行方(publisher),他们负责出版报纸。 
 374  确定了各方的身份之后,我们就可以分析每一方的职责所在。作为报纸的订阅者,,数据到来的时候我们收到通知,我们消费数据,然后我们根据数据做出反应,只要报纸到了订阅者手中,他们就可以自行处理。总而言之,订阅者要从发行方接收数据。 
 375  
 376  发行方则要发送数据。发行方也是投送房(deliver)。一般来说,一个发行方很可能有许多订阅者,同样,一个订阅者也很可能会订阅多家报社的报纸。问题的关键在于,这是一种多对多的关系,需要一种高级的抽象策略,以便订阅者能够彼此独立地发生变化,而发行方能够接受任何有消费意向的订阅者。 
 377  
 378  对于报社来说,只为给几个订阅者投送报纸就满世界跑是不划算的。而纽约市民也不可能特意飞到旧金山去拿自己订的报纸,要知道这份报纸可以直接投送到他们家门口。 
 379  订阅者要想拿到报纸的话有两种投送方式可选:推或拉。在推环境中,发行方很可能会雇佣投送人员四处送报。换句话说,他们把自己的报纸推出去,让订阅者收取。在拉环境中,规模较小的本地报社可能会在订阅者家附近的街角提供自己的数据,供订阅者“拉”。那么成长型发行方没有足够的资源进行大规模投送,因此采用拉方案,对于他们来说往往是个优化投送环节的好办法。 
 380  */ 
 381  
 382 /* 
 383  模式的实践 
 384  
 385  (1)订阅者可以订阅和退订,它们还要接受。它们可以在”由人投送(being delivered to)“和“自己收取(receiving )”之间进行选择(即推拉)。 
 386  (2)发布者负责投送,它们可以在“送出(giving)”和“由人取(being taken from)”之间进行选择 
 387  
 388  下面是一个展示发布者和订阅者之间的互动过程的高层示例。它是Sells方法的一个示例。这种技术类似于测试驱动的开发(TDD),不过它要求先写实现代码。 
 389  */ 
 390  
 391 function test() { 
 392     /* 
 393      Publishers are in charge of "publishing" i.e. creating the event 
 394      They;re alse in charge of "notifying" (firing the event) 
 395      */ 
 396     var Publisher = new Observable(); 
 397  
 398     /* 
 399      Subscribers basically ,,, "subscribe" (or listen). 
 400      Once they've been "notified" their callback functions are invoked. 
 401      */ 
 402     var Subscriber = function (news) { 
 403         // new delivered directly to my front porch 
 404     }; 
 405     Publisher.subscribeCustomer(Subscriber); 
 406  
 407     /* 
 408      Deliver a paper 
 409      sends out the new to all subscribers 
 410      */ 
 411     Publisher.deliver('extre, extre, read all about it'); 
 412  
 413     /* 
 414      That customer forget to pay his bill 
 415      */ 
 416     Publisher.unSubscribeCustomer(Subscriber); 
 417     /* 
 418      在这个模型中,可以看出发布者处于明显的主导地位。它们负责登记其顾客,而且有权停止为其投送。最后,新的报纸出版后它们会将其投送给顾客。 
 419      */ 
 420  
 421     // 下面的例子处理的事同一类问题,但发布者和订阅者之间的互动方式有所不同 
 422     /* 
 423      Newspaper vendors 
 424      setup as new Publisher obejcts 
 425      */ 
 426     var NewYorkTimes = new Publisher(); 
 427     var AustinHerald = new Publisher(); 
 428     var SfChronicle = new Publisher(); 
 429  
 430     /* 
 431      People who like to read 
 432      (Subscribers) 
 433    
 434      Each subscriber is set up as a callback method. 
 435      They all inherit from the Function prototype Object 
 436      */ 
 437     var Joe = function (from) { 
 438         console.log('Delivery from ' + from + ' to Joe'); 
 439     }; 
 440     var Lindsay = function (from) { 
 441         console.log('Delivery from ' + from + ' to Lindsay'); 
 442     }; 
 443  
 444     /* 
 445      Here we allow them to subscribe to newspaper which are the Publisher object. 
 446      */ 
 447     Joe. 
 448         subscribe(NewYorkTimes). 
 449         subscribe(SfChronicle); 
 450     Lindsay. 
 451         subscribe(AustinHerald). 
 452         subscribe(SfChronicle). 
 453         subscribe(NewYorkTimes); 
 454  
 455     /* 
 456      Then at any giving time in our application, our publishers can send off data for the subscribers to consume and react to. 
 457      */ 
 458     NewYorkTimes. 
 459         deliver('Here is your paper!direct from the Big apple'); 
 460     AustinHerald. 
 461         deliver('News'). 
 462         deliver('Reviews'). 
 463         deliver('Coupons'); 
 464 } 
 465 ; 
 466  
 467 /* 
 468  在这个例子中,发布者的创建方式和订阅者接收数据的方式没有多少改变,但拥有订阅和退订的一方变成了订阅者。当然,负责发送数据的还是发布者一方。 
 469  */ 
 470  
 471 // 扩展链式调用方法 
 472 Function.prototype.method = function (name, fn) { 
 473     this.prototype[name] = fn; 
 474     return this; 
 475 }; 
 476  
 477 // 扩展数组方法 
 478 if (!Array.prototype.forEach) { 
 479     Array.method('forEach', function (fn, thisObj) { 
 480         var scope = thisObj || window; 
 481         for (var i = 0, len = this.length; i < len; i++) { 
 482             fn.call(scope, this[i], i, this); 
 483         } 
 484     }); 
 485 } 
 486  
 487 if (!Array.prototype.filter) { 
 488     Array.method('filter', function (fn, thisObj) { 
 489         var scope = thisObj || window; 
 490         var a = []; 
 491         for (var i = 0, len = this.length; i < len; i++) { 
 492             if (!fn.call(scope, this[i], i, this)) { 
 493                 continue; 
 494             } 
 495             a.push(this[i]); 
 496         } 
 497         return a; 
 498     }); 
 499 } 
 500  
 501 Array.prototype.some = Array.prototype.some || function (fn, context) { 
 502     for (var i = this, len = this.length; i < len; i++) { 
 503         if (fn.call(context)) { 
 504             return true; 
 505         } 
 506     } 
 507     return false; 
 508 }; 
 509  
 510 // demo: 
 511 function isBigEnough(element, index, array) { 
 512     return (element >= 10); 
 513 } 
 514 var filtered = [12, 5, 8, 130, 44].filter(isBigEnough); 
 515 // 12, 130, 44 
 516  
 517  
 518 // 添加观察者系统 
 519 window.DED = window.DED || {}; 
 520 DED.util = DED.util || {}; 
 521 DED.util.Observer = function () { 
 522     this.fns = []; 
 523 }; 
 524 DED.util.Observer.prototype = { 
 525     subscribe: function (fn) { 
 526         this.fns.push(fn); 
 527     }, 
 528     unsubscribe: function (fn) { 
 529         // 取消订阅当前函数 
 530         this.fns = this.fns.filter(function (el) { 
 531             if (el !== fn) { 
 532                 return el; 
 533             } 
 534         }); 
 535     }, 
 536     fire: function (o) { 
 537         // 触发(运行)所有保存的函数 
 538         this.fns.forEach(function (el) { 
 539             el(o); 
 540         }); 
 541     } 
 542 }; 
 543  
 544 // 构建观察者API 
 545 function Publisher() { 
 546     this.subscribers = []; 
 547 } 
 548 // 投送方法 
 549 Publisher.prototype.deliver = function (data) { 
 550     this.subscribers.forEach(function (fn) { 
 551         fn(data); 
 552     }); 
 553     return this; 
 554 }; 
 555 // 订阅方法 
 556 Function.prototype.subscribe = function (publisher) { 
 557     var that = this; 
 558     var alreadyExists = publisher.subscribers.some(function (el) { 
 559         return el === that; 
 560     }); 
 561     if (!alreadyExists) { 
 562         publisher.subscribers.push(this); 
 563     } 
 564     return this; 
 565 }; 
 566 // 退订方法 
 567 Function.prototype.unsubscribe = function (publisher) { 
 568     var that = this; 
 569     publisher.subscribers = publisher.subscribers.filter(function (el) { 
 570         return el !== that; 
 571     }); 
 572     return this; 
 573 }; 
 574  
 575 // 有些订阅者在监听到某种一次性的事件之后会在回调阶段立即退订事件: 
 576 var publisherObject = new Publisher(); 
 577 var observerObject = function (data) { 
 578     // process data 
 579     console.log(data); 
 580     // unsubscribe from this publisher 
 581     arguments.callee.unsubscribe(publisherObject); 
 582 }; 
 583 observerObject.subscribe(publisherObject); 
 584 publisherObject.deliver('This is news'); 
 585  
 586  
 587 // 示例:动画 
 588 // Publisher API 
 589 var Animation = function (o) { 
 590     this.onStart = new Publisher(); 
 591     this.onComplete = new Publisher(); 
 592     this.onTween = new Publisher(); 
 593 }; 
 594 Animation.prototype = { 
 595     fly: function () { 
 596         // begin animation 
 597         this.onStart.deliver(); 
 598         /* 
 599          for(...) { // loop through frames 
 600          //deliver frame number 
 601          this.onTween.deliver(i); 
 602          } 
 603          */ 
 604         // end animation 
 605         this.onComplete.deliver(); 
 606     } 
 607 }; 
 608  
 609 // setup an account with the animation manager 
 610 var superman = new Animation({ 
 611     // config properties 
 612 }); 
 613  
 614 // Begin implementing subscribers 
 615 var putOnCape = function (i) { 
 616 }; 
 617 var takeOffCape = function (i) { 
 618 }; 
 619  
 620 putOnCape.subscribe(superman.onStart); 
 621 takeOffCape.subscribe(superman.onComplete); 
 622  
 623 function main() { 
 624     // fly can be called anywhere 
 625     superman.fly(); 
 626     // for instance 
 627     addEvent(element, 'click', function () { 
 628         superman.fly(); 
 629     }); 
 630 } 
 631  
 632 (function () { 
 633     // 变形示例,通知相应的观察者或部分观察者 
 634  
 635     function WatcherObserver() { 
 636         this.job = ''; 
 637     } 
 638  
 639     WatcherObserver.prototype = { 
 640         update: function (subject) { 
 641             // 拉模型 
 642             console.log(this.job + '获取到通知,当前污染级别为:' + subject.getPolluteLevel()); 
 643         } 
 644     }; 
 645  
 646     function WaterQualitySubject() { 
 647         this.polluteLevel = 0; 
 648         this.observers = []; 
 649     } 
 650  
 651     WaterQualitySubject.prototype = { 
 652         attach: function (observer) { 
 653             this.observers.push(observer); 
 654         }, 
 655         detach: function (observer) { 
 656             for (var i = 0, len = this.observers.length; i < len; i++) { 
 657                 if (observer === this.observers[i]) { 
 658                     this.observers.splice(i, 1); 
 659                     return; 
 660                 } 
 661             } 
 662         }, 
 663         notifyWatchers: function () { 
 664             var watcher; 
 665             for (var i = 0, len = this.observers.length; i < len; i++) { 
 666                 watcher = this.observers[i]; 
 667                 if (this.polluteLevel >= 0) { 
 668                     if (watcher.job === '检测人员') { 
 669                         watcher.update(this); 
 670                     } 
 671                 } 
 672  
 673                 if (this.polluteLevel >= 1) { 
 674                     if (watcher.job === '预警人员') { 
 675                         watcher.update(this); 
 676                     } 
 677                 } 
 678  
 679                 if (this.polluteLevel >= 2) { 
 680                     if (watcher.job === '检测部门领导') { 
 681                         watcher.update(this); 
 682                     } 
 683                 } 
 684             } 
 685         }, 
 686         getPolluteLevel: function () { 
 687             return this.polluteLevel; 
 688         }, 
 689         setPolluteLevel: function (level) { 
 690             this.polluteLevel = level; 
 691             this.notifyWatchers(); 
 692         } 
 693     }; 
 694  
 695     new function () { 
 696         var subject = new WaterQualitySubject(); 
 697         var watch1 = new WatcherObserver(); 
 698         watch1.job = '检测人员'; 
 699         var watch2 = new WatcherObserver(); 
 700         watch2.job = '预警人员'; 
 701         var watch3 = new WatcherObserver(); 
 702         watch3.job = '检测部门领导'; 
 703  
 704         subject.attach(watch1); 
 705         subject.attach(watch2); 
 706         subject.attach(watch3); 
 707  
 708         console.log('当水质为正常的时候--------------------'); 
 709         subject.setPolluteLevel(0); 
 710         console.log('当水质为轻度的时候--------------------'); 
 711         subject.setPolluteLevel(1); 
 712         console.log('当水质为中度的时候--------------------'); 
 713         subject.setPolluteLevel(2); 
 714     }(); 
 715 }()); 
 716  
 717 /** 
 718  * 优点 
 719  * 
 720  * 1.观察者模式实现了观察者和目标之间的抽象耦合 
 721  * 原本目标对象在状态发生改变的时候,需要直接调用所有的观察者对象,但是抽象出观察者接口以后,目标和观察者就只是在抽象层面上耦合了,也就是说目标只是知道观察者接口,并不知道具体的观察者的类,从而实现目标类和具体的观察者类之间解耦。 
 722  * 
 723  * 2.观察者模式实现了动态联动 
 724  * 所谓联动,就是做一个操作会引起其他相关的操作。由于观察者模式对观察者注册实现管理,那就可以在运行期间,通过动态地控制注册的观察者,来控制某个动作的联动范围,从而实现动态联动。 
 725  * 
 726  * 3.观察者模式支持广播通信 
 727  * 由于目标发送通知给观察者是面向所有注册的观察者,所以每次目标通知的信息就要对所有注册的观察者进行广播。当然,也可以通过在目标上添加新的功能来限制广播的范围。 
 728  * 在广播通信的时候要注意一个问题,就是相互广播造成死循环的问题。比如A和B两个对象互为观察者和目标对象。 
 729  * 
 730  * 缺点 
 731  * 
 732  * 1.可能会引起无谓的操作 
 733  * 由于观察者模式每次都是广播通信,不管观察者需不需要,每个观察者都会被调用update方法,如果观察者不需要执行相应处理,那么这次操作就浪费了,甚至引起误更新。比如,本应该在执行这次状态更新前把某个观察者删除掉,这样通知的时候就没有这个观察者了,但是却忘了,那么就会引起误操作。 
 734  * 
 735  * 相关模式 
 736  * 
 737  * 观察者模式和状态模式 
 738  * 有相似之处。 
 739  * 观察者模式是当目标状态发生改变时,触发并通知观察者,让观察者去执行相应的操作。而状态模式是根据不同的状态,选择不同的实现,这个现实类的主要功能就是针对状态相应地操作,它不像观察者,观察者本身还有很多其他的功能,接收通知并执行相应处理知识观察者的部分功能。 
 740  * 当然观察者模式和状态模式是可以结合使用的。观察者模式的重心在触发联动,但是到底决定哪些观察者会被联动,这时就可以采用状态模式来实现了,也可以采用策略模式来进行选择需要联动的观察者。 
 741  * 
 742  * 观察者模式和中介者模式 
 743  * 可以结合使用。 
 744  * 如果观察者和被观察者之间的交互关系很复杂,比如,省级三级联动。这种情况下,很明显需要相关的状态都联动准备好了,然后再一次性地通知观察者。可以使用中介者模式来封装观察者和目标的关系。 
 745  * 在应用里面,比如,把一个界面所有的事件用一个对象来处理,把一个组件触发事件后,需要操作其他组件的动作都封装到一起,这个对象就是中介者了。 
 746  */ 
 747  
 748 // Javscript Patterns example: 
 749 /* Title: Observer 
 750  Description: is a publish/subscribe pattern which allows a number of observer objects to see an event 
 751  */ 
 752 var Observer = { 
 753     addSubscriber: function (callback) { 
 754         this.subscribers[this.subscribers.length] = callback; 
 755     }, 
 756     removeSubscriber: function (callback) { 
 757         for (var i = 0; i < this.subscribers.length; i++) { 
 758             if (this.subscribers[i] === callback) { 
 759                 delete this.subscribers[i]; 
 760             } 
 761         } 
 762     }, 
 763     publish: function () { 
 764         for (var i = 0; i < this.subscribers.length; i++) { 
 765             if (typeof this.subscribers[i] === 'function') { 
 766                 this.subscribers[i].apply(this, arguments); 
 767             } 
 768         } 
 769     }, 
 770     make: function (o) { 
 771         for (var i in this) { 
 772             o[i] = this[i]; 
 773             o.subscribers = []; 
 774         } 
 775     } 
 776 }; 
 777  
 778 var blogger = { 
 779     writeBlogPost: function () { 
 780         var content = 'Today is ' + new Date(); 
 781         this.publish(content); 
 782     } 
 783 }; 
 784  
 785 var la_times = { 
 786     newIssue: function () { 
 787         var paper = 'Martians have landed on Earth.'; 
 788         this.publish(paper); 
 789     } 
 790 }; 
 791  
 792 Observer.make(blogger); 
 793 Observer.make(la_times); 
 794  
 795 var jack = { 
 796     read: function (what) { 
 797         console.log('I just read that ' + what); 
 798     } 
 799 }; 
 800  
 801 var jill = { 
 802     gossip: function (what) { 
 803         console.log('You didn\'t hear it from me, but' + what); 
 804     } 
 805 }; 
 806  
 807 blogger.addSubscriber(jack.read); 
 808 blogger.addSubscriber(jill.gossip); 
 809 blogger.writeBlogPost(); 
 810  
 811 blogger.removeSubscriber(jill.gossip); 
 812 blogger.writeBlogPost(); 
 813  
 814 la_times.addSubscriber(jill.gossip); 
 815 la_times.newIssue(); 
 816  
 817  
 818 // http://www.joezimjs.com/javascript/javascript-design-patterns-observer/ 
 819 // Now we’ll implement the pull method of the observer pattern. 
 820 // When you’re using the pull method, 
 821 // it makes more sense to swap things around a bit: 
 822 Observable = function () { 
 823     this.status = "constructed"; 
 824 } 
 825 Observable.prototype.getStatus = function () { 
 826     return this.status; 
 827 } 
 828  
 829 Observer = function () { 
 830     this.subscriptions = []; 
 831 } 
 832 Observer.prototype = { 
 833     subscribeTo: function (observable) { 
 834         this.subscriptions.push(observable); 
 835     }, 
 836     unsubscribeFrom: function (observable) { 
 837         var i = 0, 
 838             len = this.subscriptions.length; 
 839  
 840         // Iterate through the array and if the observable is 
 841         // found, remove it. 
 842         for (; i < len; i++) { 
 843             if (this.subscriptions[i] === observable) { 
 844                 this.subscriptions.splice(i, 1); 
 845                 // Once we've found it and removed it, we 
 846                 // don't need to continue, so just return. 
 847                 return; 
 848             } 
 849         } 
 850     }, 
 851     doSomethingIfOk: function () { 
 852         var i = 0, 
 853             len = this.subscriptions.length; 
 854  
 855         // Iterate through the subscriptions and determine 
 856         // whether the status has changed to ok on each of them, 
 857         // and do something for each subscription that has 
 858         for (; i < len; i++) { 
 859             if (this.subscriptions[i].getStatus() === "ok") { 
 860                 // Do something because the status of the 
 861                 // observable is what we want it to be 
 862             } 
 863         } 
 864     } 
 865 } 
 866  
 867 var observer = new Observer(), 
 868     observable = new Observable(); 
 869 observer.subscribeTo(observable); 
 870  
 871 // Nothing will happen because the status hasn't changed 
 872 observer.doSomethingIfOk(); 
 873  
 874 // Change the status to "ok" so now something will happen 
 875 observable.status = "ok"; 
 876 observer.doSomethingIfOk(); 
 877  
 878 </script> 
 879  
 880 <select name="country" id="country"> 
 881     <option value="01">01</option> 
 882     <option value="02">02</option> 
 883     <option value="03">03</option> 
 884     <option value="04">04</option> 
 885     <option value="05">05</option> 
 886 </select> 
 887 <select name="province" id="province"> 
 888     <option value="001">001</option> 
 889     <option value="002">002</option> 
 890     <option value="003">003</option> 
 891     <option value="004">004</option> 
 892     <option value="005">005</option> 
 893 </select> 
 894 <select name="city" id="city"> 
 895     <option value="0001">0001</option> 
 896     <option value="0002">0002</option> 
 897     <option value="0003">0003</option> 
 898     <option value="0004">0004</option> 
 899     <option value="0005">0005</option> 
 900 </select> 
 901  
 902 <script> 
 903     (function () { 
 904         // 观察者模式和中介者模式组合使用 
 905  
 906         var Mediator = { 
 907             changed: function (colleague) { 
 908                 switch (colleague.element.id) { 
 909                     case 'country': 
 910                         this.logProvinceChange(); 
 911                         break; 
 912                     case 'province': 
 913                         this.logCityChange(); 
 914                         break; 
 915                 } 
 916             }, 
 917             logProvinceChange: function () { 
 918                 console.log('省份联动了,当前值为' + this.province.element.value); 
 919             }, 
 920             logCityChange: function () { 
 921                 console.log('市联动了,当前值为' + this.city.element.value); 
 922             } 
 923         }; 
 924  
 925         function CountrySelect(element, mediator) { 
 926             this.observers = []; 
 927             this.element = element; 
 928             this.mediator = mediator; 
 929             var me = this; 
 930             this.element.addEventListener('change', function (e) { 
 931                 me.notifyObservers(); 
 932             }, false); 
 933         } 
 934  
 935         CountrySelect.prototype = { 
 936             attach: function (observer) { 
 937                 this.observers.push(observer); 
 938             }, 
 939             detach: function (observer) { 
 940                 for (var i = 0, len = this.observers.length; i < len; i++) { 
 941                     if (observer === this.observers[i]) { 
 942                         this.observers.splice(i, 1); 
 943                         return; 
 944                     } 
 945                 } 
 946             }, 
 947             notifyObservers: function () { 
 948                 for (var i = 0, len = this.observers.length; i < len; i++) { 
 949                     this.observers[i].update(this); 
 950                 } 
 951  
 952                 this.mediator.changed(this); 
 953             } 
 954         }; 
 955  
 956         function ProvinceSelect(element, mediator) { 
 957             this.observers = []; 
 958             this.element = element; 
 959             this.mediator = mediator; 
 960             var me = this; 
 961             this.element.addEventListener('change', function (e) { 
 962                 me.notifyObservers(); 
 963             }, false); 
 964         } 
 965  
 966         ProvinceSelect.prototype = { 
 967             attach: function (observer) { 
 968                 this.observers.push(observer); 
 969             }, 
 970             detach: function (observer) { 
 971                 for (var i = 0, len = this.observers.length; i < len; i++) { 
 972                     if (observer === this.observers[i]) { 
 973                         this.observers.splice(i, 1); 
 974                         return; 
 975                     } 
 976                 } 
 977             }, 
 978             notifyObservers: function () { 
 979                 for (var i = 0, len = this.observers.length; i < len; i++) { 
 980                     this.observers[i].update(this); 
 981                 } 
 982  
 983                 this.mediator.changed(this); 
 984             }, 
 985  
 986             update: function (subject) { 
 987                 var index = parseInt(subject.element.value, 10) - 1; 
 988                 this.element.selectedIndex = index; 
 989  
 990                 this.notifyObservers(); 
 991             } 
 992         }; 
 993  
 994         function CitySelect(element) { 
 995             this.element = element; 
 996         } 
 997  
 998         CitySelect.prototype = { 
 999             update: function (subject) { 
1000                 var index = parseInt(subject.element.value, 10) - 1; 
1001                 this.element.selectedIndex = index; 
1002             } 
1003         }; 
1004  
1005         var country = new CountrySelect(document.getElementById('country'), Mediator); 
1006         var province = new ProvinceSelect(document.getElementById('province'), Mediator); 
1007         var city = new CitySelect(document.getElementById('city')); 
1008  
1009         Mediator.province = province; 
1010         Mediator.city = city; 
1011  
1012         country.attach(province); 
1013         province.attach(city); 
1014     }()); 
1015 </script> 
1016 </body> 
1017 </html>

本文参考链接:https://www.cnblogs.com/webFrontDev/archive/2013/05/13/3075864.html