define

The observer pattern defines pair-to-pair dependencies between objects, and when an object changes state, all of its dependencies are notified and updated automatically.

Compared to other modes, this mode adds one more principle:

  • Loosely coupled design between interacting objects

So the main idea is that we should have one big object to handle subscriptions, and many other objects that are inescapable and wait for events to occur.

Drawing1

Another important point is that Observers are astute in the order in which they receive messages, so you should not follow the order that they are added.

Based on example

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 2Copy the code

If you want to delete with some form of ID instead of passing in a callback, the code needs to look like this:

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')Copy the code

pull vs push

The observer model has two important strategies:

  • Push – Notifies all observers when an event occurs and passes all new data to them

  • Pull – All observers will be notified when an event occurs, and each observer will Pull the data it needs

When you only want the data you need, the Pull method is preferable. In this example, the Subject will notify the observer of the change, and each observer will take the data they need. And in this case, we hide the observer array and store their private data in a closure.

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 2Copy the code

The theme of the observer model

To avoid creating multiple observables, it is best to add theme functionality to the observer mode. The simplest form looks like this:

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 2Copy the code

More advanced features include the following:

  • Subtopics (e.g. /bar/green or bar.green)

  • Publish to topics propagate to subtopics

  • Publish to all topics

  • Subscriber priority

Jquery.callback for observer mode

JQuery has a nice feature called $.callback. In addition to the classic observation function, he has another set of markers:

  • Once: Ensures that the callback list can only be triggered once

  • Memory: Traces data

  • Unique: Ensures that the callback function is added only once.

  • StopOnFalse: The call is interrupted when the callback function returns false.

Using these options you can customize your viewer. Let’s look at the most basic example:

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! ');Copy the code

If you want to see some complicated examples, click here.

CoffeeScript example

Here is a simple example, and an almost identical example can be found in the 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()Copy the code

data

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

  2. (Book) Head First Design Patterns

  3. Learning JavaScript Design Patterns

  4. (Book) 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

Original text: bumbu. Making. IO/javascript -… Translator: Miao Yu