JavaScript 觀察者 (發佈/訂閱) 模式

定義

觀察者模式定義了對象之間一對對多的依賴關係,當一個對象改變了狀態,它的全部依賴會被通知,而後自動更新。javascript

和其餘模式相比,這種模式又增長了一個原則:html

  • 在相互做用的對象之間進行鬆散耦合設計

因此主要的想法是咱們要有一個大的對象來處理訂閱(Subject/Observable),以及不少對象(Observers)被訂閱而後等待事件觸發。java

Drawing1
Drawing1

還有一個重要的點就是Observers接受消息的順序是隨機的,因此你不要依照Observers添加的順序。jquery

基礎例子

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 1

Observable.removeObserver(observer)

Observable.notifyObservers('test 2')
// First observer message:test 2複製代碼

若是你想用某種形式的ID來刪除,而不是傳入回調函數,那麼代碼須要改爲下面這樣:git

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

觀察者模式有兩種重要的策略:github

  • Push - 當一個事件發生後將通知全部觀察者,並把全部的新數據傳給他們api

  • Pull - 當一個事件發生後將通知全部觀察者,每一個觀察者將拉取本身須要的數據數組

當你只想要你須要的數據,Pull 方法更爲可取,在下面這個例子中,Subject將會通知觀察者發生了改變,而後每一個觀察者取它們本身須要的數據。而且在這個例子中,咱們隱藏觀察者數組,而且將各自的私有數據存到閉包中。bash

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 1

Observable.updatePrivate('test 2')
// Private value is: test 2複製代碼

觀察者模式之主題

爲了避免建立多個可觀察對象,最好在觀察者模式中加入主題功能,最簡單的形式看起來是這樣的:markdown

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 1

Observable.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 @subscribers

class 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.onUpdate
observable.subscribe observer2.onUpdate
observable.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

原文:bumbu.github.io/javascript-…譯者:繆宇

相關文章
相關標籤/搜索