[聚合文章] JavaScript 观察者 (发布/订阅) 模式

JavaScript 2017-11-26 19 阅读

定义

观察者模式定义了对象之间一对对多的依赖关系,当一个对象改变了状态,它的所有依赖会被通知,然后自动更新。

和其他模式相比,这种模式又增加了一个原则:

  • 在相互作用的对象之间进行松散耦合设计

所以主要的想法是我们要有一个大的对象来处理订阅(Subject/Observable),以及很多对象(Observers)被订阅然后等待事件触发。

Drawing1

还有一个重要的点就是Observers接受消息的顺序是随机的,所以你不要依照Observers添加的顺序。

基础例子

var Observable = {    observers: []  , addObserver: function(observer) {      this.observers.push(observer)    }  , removeObserver: function(observer) {      var index = this.observers.indexOf(observer)      if (~index) {        this.observers.splice(index, 1)      }    }  , notifyObservers: function(message) {      for (var i = this.observers.length - 1; i >= 0; i--) {        this.observers[i](message)      };    }  }Observable.addObserver(function(message){  console.log("First observer message:" + message)})var observer = function(message){  console.log("Second observer message:" + message)}Observable.addObserver(observer)Observable.notifyObservers('test 1')// Second observer message:test 1// First observer message:test 1Observable.removeObserver(observer)Observable.notifyObservers('test 2')// First observer message:test 2

如果你想用某种形式的ID来删除,而不是传入回调函数,那么代码需要改成下面这样:

var Observable = {    observers: []  , lastId: -1  , addObserver: function(observer) {      this.observers.push({        callback: observer      , id: ++this.lastId      })      return this.lastId    }  , removeObserver: function(id) {      for (var i = this.observers.length - 1; i >= 0; i--) {        this.observers[i]        if (this.observers[i].id == id) {          this.observers.splice(i, 1)          return true        }      }      return false    }  , notifyObservers: function(message) {      for (var i = this.observers.length - 1; i >= 0; i--) {        this.observers[i].callback(message)      };    }  }var id_1 = Observable.addObserver(function(message){  console.log("First observer message:" + message)})var observer = function(message){  console.log("Second observer message:" + message)}var id_2 = Observable.addObserver(observer)Observable.notifyObservers('test 1')Observable.removeObserver(id_2)Observable.notifyObservers('test 2')

pull vs push

观察者模式有两种重要的策略:

  • Push - 当一个事件发生后将通知所有观察者,并把所有的新数据传给他们

  • Pull - 当一个事件发生后将通知所有观察者,每个观察者将拉取自己需要的数据

当你只想要你需要的数据,Pull 方法更为可取,在下面这个例子中,Subject将会通知观察者发生了改变,然后每个观察者取它们自己需要的数据。并且在这个例子中,我们隐藏观察者数组,并且将各自的私有数据存到闭包中。

var Observable = {};(function(O){  var observers = []    , privateVar  O.addObserver = function(observer) {    observers.push(observer)  }  O.removeObserver = function(observer) {    var index = observers.indexOf(observer)    if (~index) {      observers.splice(index, 1)    }  }  O.notifyObservers = function() {    for (var i = observers.length - 1; i >= 0; i--) {      observers[i].update()    };  }  O.updatePrivate = function(newValue) {    privateVar = newValue    this.notifyObservers()  }  O.getPrivate = function() {    return privateVar  }}(Observable))Observable.addObserver({  update: function(){    this.process()  }, process: function(){    var value = Observable.getPrivate()    console.log("Private value is: " + value)  }})Observable.updatePrivate('test 1')// Private value is: test 1Observable.updatePrivate('test 2')// Private value is: test 2

观察者模式之主题

为了不创建多个可观察对象,最好在观察者模式中加入主题功能,最简单的形式看起来是这样的:

var Observable = {    observers: []  , addObserver: function(topic, observer) {      this.observers[topic] || (this.observers[topic] = [])      this.observers[topic].push(observer)    }  , removeObserver: function(topic, observer) {      if (!this.observers[topic])        return;      var index = this.observers[topic].indexOf(observer)      if (~index) {        this.observers[topic].splice(index, 1)      }    }  , notifyObservers: function(topic, message) {      if (!this.observers[topic])        return;      for (var i = this.observers[topic].length - 1; i >= 0; i--) {        this.observers[topic][i](message)      };    }  }Observable.addObserver('cart', function(message){  console.log("First observer message:" + message)})Observable.addObserver('notificatons', function(message){  console.log("Second observer message:" + message)})Observable.notifyObservers('cart', 'test 1')// First observer message:test 1Observable.notifyObservers('notificatons', 'test 2')// Second observer message:test 2

更高级的功能会有如下特性:

  • 子话题 (比如 /bar/green 或者 bar.green)

  • 发布到主题传播到子主题

  • 发布到所有主题

  • 订阅者优先级

观察者模式之jQuery.Callback

jQuery有一个很好的特性,$.Callback。除了经典的观察功能外,他还有其他的一组标记

  • once: 确保callback列表只能被触发一次

  • memory: 跟踪数据

  • unique: 确保回调函数只会被添加一次。

  • stopOnFalse: 当回调函数返回false,则中断调用。

使用这些选项你可以定制的你的观察者。让我们来看看最基础的例子:

var callbacks = jQuery.Callbacks()  , Topic = {      publish: callbacks.fire,      subscribe: callbacks.add,      unsubscribe: callbacks.remove    }function fn1( value ){  console.log( "fn1: " + value );}function fn2( value ){  console.log("fn2: " + value);}Topic.subscribe(fn1);Topic.subscribe(fn2);Topic.publish('hello world!');Topic.publish('woo! mail!');

如果你想看一些复杂的例子,请点这里

CoffeeScript例子

下面是一个简单的例子,几乎相同的例子可以在“CoffeeScript Cookbook [7]”中找到。

class Observable    constructor: () ->        @subscribers = []    subscribe: (callback) ->        @subscribers.push callback    unsubscribe: (callback) ->        @subscribers = @subscribers.filter (item) -> item isnt callback    notify: () ->        subscriber() for subscriber in @subscribersclass Observer1    onUpdate: () ->        console.log "1st got new message"class Observer2    onUpdate: () ->        console.log "2nd updated"observable = new Observable()observer1 = new Observer1()observer2 = new Observer2()observable.subscribe observer1.onUpdateobservable.subscribe observer2.onUpdateobservable.notify()

资料

  1. (github) shichuan / javascript-patterns / design-patterns / observer.html and jQuery examples

  2. (书) Head First Design Patterns

  3. (书) Learning JavaScript Design Patterns

  4. (书) JavaScript Patterns: Build Better Applications with Coding and Design Patterns

  5. (书) Learning JavaScript Design Patterns: A JavaScript and jQuery Developer's Guide

  6. (书) Pro JavaScript Design Patterns: The Essentials of Object-Oriented JavaScript Programming

  7. (书) CoffeeScript Cookbook

  8. (文章) dofactory JavaScript Observer Pattern

    注:本文内容来自互联网,旨在为开发者提供分享、交流的平台。如有涉及文章版权等事宜,请你联系站长进行处理。