观察者形式与宣布/定阅形式

视察者形式与宣布/定阅形式

视察者形式

观点

一个被视察者的对象,经由过程注册的体式格局保护一组视察者对象。当被视察者发作变化,就会发作一个关照,经由过程播送的体式格局发送出去,末了挪用每一个视察者的更新要领。当视察者不再须要接收被视察者的关照时,被视察者能够将该视察者从所保护的组中删除。

完成

这个完成包括以下组件:

  • 被视察者:保护一组视察者, 供运用于增添和移除视察者的要领
  • 视察者:供应一个更新接口,用于当被视察者状况变化时,获得关照
  • 详细的被视察者:状况变化时播送关照给视察者,坚持详细的视察者的信息
  • 详细的视察者:坚持一个指向详细被视察者的援用,完成一个更新接口,用于视察,以便保证本身状况老是和被视察者状况一致的
  1. 起首,对被视察者保护的一组视察者(列表)举行建模

    function ObserverList() {
      this.observerList = []
    }
    
    ObserverList.prototype.add = function(obj) {
      return this.observerList.push(obj)
    }
    
    ObserverList.prototype.Empty = function() {
      this.observerList = []
    }
    
    ObserverList.prototype.removeAt = function(index) {
      this.observerList.splice(index, 1)
    }
    
    ObserverList.prototype.count = function() {
      return this.observerList.length
    }
    
    ObserverList.prototype.get = function(index) {
      if (index > -1 && index < this.observerList.length) {
        return this.observerList[index]
      }
    }
    
    // Extend an object with an extension
    function extend(extension, obj) {
      for (var key in extension) {
        obj[key] = extension[key]
      }
    }
  2. 接着,对被视察者以及其增添、删除、关照才能举行建模

    function Subject() {
      this.observers = new ObserverList()
    }
    
    Subject.prototype.addObserver = function(observer) {
      this.observers.add(observer)
    }
    
    Subject.prototype.removeObserver = function(observer) {
      this.observers.removeAt(this.observers.IndexOf(observer, 0))
    }
    
    Subject.prototype.notify = function(context) {
      var observerCount = this.observers.count()
    
      for (var i = 0; i < observerCount; i++) {
        this.observers.get(i).update(context)
      }
    }
  3. 接着,对视察者举行建模,这里的 update 函数以后会被详细的行动掩盖

    function Observer() {
      this.update = function() {
        // ...
      }
    }

样例运用

我们运用上面的视察者组件,如今我们定义

  • 一个按钮,这个按钮用于增添新的充任视察者的挑选框到页面上
  • 一个掌握用的挑选框 , 充任一个被视察者,关照别的挑选框是不是应当被选中
  • 一个容器,用于安排新的挑选框

    <button id="addNewObserver">Add New Observer checkbox</button>
    <input id="mainCheckbox" type="checkbox"/>
    <div id="observersContainer"></div>
    // DOM 元素的援用
    var controlCheckbox = document.getElementById('mainCheckbox'),
      addBtn = document.getElementById('addNewObserver'),
      container = document.getElementById('observersContainer')
    
    // 详细的被视察者
    
    // Subject 类扩大 controlCheckbox
    extend(new Subject(), controlCheckbox)
    
    //点击 checkbox 将会触发对视察者的关照
    controlCheckbox['onclick'] = new Function(
      'controlCheckbox.notify(controlCheckbox.checked)'
    )
    
    addBtn['onclick'] = AddNewObserver
    
    // 详细的视察者
    
    function AddNewObserver() {
      // 竖立一个新的用于增添的 checkbox
      var check = document.createElement('input')
      check.type = 'checkbox'
    
      // 运用 Observer 类扩大 checkbox
      extend(new Observer(), check)
    
      // 运用定制的 update 函数重载
      check.update = function(value) {
        this.checked = value
      }
    
      // 增添新的视察者到我们重要的被视察者的视察者列表中
      controlCheckbox.AddObserver(check)
    
      // 将元素增加到容器的末了
      container.appendChild(check)
    }

上述示例中

  • Subject 类扩大 controlCheckbox,所以 controlCheckbox 是详细的被视察者
  • 点击 addBtn 时,天生一个新的 check,check 被 Observer 类所拓展并重写了 update 要领,所以 check 是详细的视察者,末了 controlCheckbox 将 check 增加到了 controlCheckbox 所保护的视察者列表中
  • 点击 controlCheckbox,挪用了被视察者的 notify 要领,进而触发了 controlCheckbox 中所保护的视察者的 update 要领

宣布/定阅形式

完成

var pubsub = {}

;(function(q) {
  var topics = {},
    subUid = -1

  // Publish or broadcast events of interest
  // with a specific topic name and arguments
  // such as the data to pass along
  q.publish = function(topic, args) {
    if (!topics[topic]) {
      return false
    }

    var subscribers = topics[topic],
      len = subscribers ? subscribers.length : 0

    while (len--) {
      subscribers[len].func(topic, args)
    }

    return this
  }

  // Subscribe to events of interest
  // with a specific topic name and a
  // callback function, to be executed
  // when the topic/event is observed
  q.subscribe = function(topic, func) {
    if (!topics[topic]) {
      topics[topic] = []
    }

    var token = (++subUid).toString()
    topics[topic].push({
      token: token,
      func: func
    })
    return token
  }

  // Unsubscribe from a specific
  // topic, based on a tokenized reference
  // to the subscription
  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 this
  }
})(pubsub)

样例运用 1

// Another simple message handler

// A simple message logger that logs any topics and data received through our
// subscriber
var messageLogger = function(topics, data) {
  console.log('Logging: ' + topics + ': ' + data)
}

// Subscribers listen for topics they have subscribed to and
// invoke a callback function (e.g messageLogger) once a new
// notification is broadcast on that topic
var subscription = pubsub.subscribe('inbox/newMessage', messageLogger)

// Publishers are in charge of publishing topics or notifications of
// interest to the application. e.g:

pubsub.publish('inbox/newMessage', 'hello world!')

// or
pubsub.publish('inbox/newMessage', ['test', 'a', 'b', 'c'])

// or
pubsub.publish('inbox/newMessage', {
  sender: 'hello@google.com',
  body: 'Hey again!'
})

// We cab also unsubscribe if we no longer wish for our subscribers
// to be notified
// pubsub.unsubscribe( subscription );

// Once unsubscribed, this for example won't result in our
// messageLogger being executed as the subscriber is
// no longer listening
pubsub.publish('inbox/newMessage', 'Hello! are you still there?')

样例运用 2

旧的代码

$.ajax('http:// xxx.com?login', function(data) {
  header.setAvatar(data.avatar) // 设置 header 模块的头像
  nav.setAvatar(data.avatar) // 设置导航模块的头像
})

运用了宣布/定阅形式的代码

$.ajax('http:// xxx.com?login', function(data) {
  pubsub.publish('loginSucc', data) // 宣布登录胜利的音讯
})

// header 模块
var header = (function() {
  pubsub.subscribe('loginSucc', function(data) {
    header.setAvatar(data.avatar)
  })

  return {
    setAvatar: function(data) {
      console.log('设置 header 模块的头像')
    }
  }
})()

// nav 模块
var nav = (function() {
  pubsub.subscribe('loginSucc', function(data) {
    nav.setAvatar(data.avatar)
  })

  return {
    setAvatar: function(avatar) {
      console.log('设置 nav 模块的头像')
    }
  }
})()

长处

  1. 能够运用于异步编程中,比方 ajax 请求的 succ、error 等事宜中,或许动画的每一帧完成以后去宣布一个事宜,从而不须要过量关注对象再异步运转时期的内部状况
  2. 庖代对象之间硬编码的关照机制,一个对象不再显现地挪用别的一个对象的某个接口,让这两个对象松耦合的联络在一起

瑕玷

  1. 建立定阅者需斲丧肯定内存,当你定阅一个音讯后,纵然音讯直到末了都未发作,但这个定阅者也会一直存在于内存中
  2. 宣布/定阅形式弱化对象之间的联络,对象和对象之间的必要联络也被深埋在背地,致使顺序难以跟踪保护和明白

两者的差别

  • 视察者形式请求想要接收相干关照的视察者必须到提议这个事宜的被视察者上注册这个事宜

    《观察者形式与宣布/定阅形式》

    controlCheckbox.AddObserver(check)
  • 宣布/定阅形式运用一个主题/事宜频道,这个频道处于定阅者和宣布者之间,这个事宜体系许可代码定义运用相干的事宜,防止定阅者和宣布者之间的依赖性

    《观察者形式与宣布/定阅形式》

    pubsub.subscribe('inbox/newMessage', messageLogger)
    pubsub.publish('inbox/newMessage', 'hello world!')

参考资料

  1. 《JavaScript设想形式》 作者:Addy Osmani
  2. 《JavaScript设想形式与开辟实践》 作者:曾探
  3. 设想形式(三):视察者形式与宣布/定阅形式区分
    原文作者:3santiago3
    原文地址: https://segmentfault.com/a/1190000019185918
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞