JS 觀察者模式

1. 介紹

觀察者模式又叫發佈訂閱模式(Publish/Subscribe),它定義了一種一對多的關係,讓多個觀察者對象同時監聽某一個主題對象,這個主題對象的狀態發生變化時就會通知全部的觀察者對象,使得它們可以自動更新本身。html

使用觀察者模式的好處:前端

  1. 支持簡單的廣播通訊,自動通知全部已經訂閱過的對象。
  2. 目標對象與觀察者存在的是動態關聯,增長了靈活性。
  3. 目標對象與觀察者之間的抽象耦合關係可以單獨擴展以及重用。

2. 實現一

以下例子:segmentfault

  • subscribers:含有不一樣 type 的數組,存儲有全部訂閱者的數組,訂閱行爲將被加入到這個數組中
  • subscribe:方法爲將訂閱者添加到 subscribers 中對應的數組中
  • unsubscribe:方法爲在 subscribers 中刪除訂閱者
  • publish:循環遍歷 subscribers 中的每一個元素,並調用他們註冊時提供的方法
let publisher = {
  subscribers: {
    any: []
  },
  subscribe: function(fn, type = 'any') {
    if (typeof this.subscribers[type] === 'undefined') {
      this.subscribers[type] = []
    }
    this.subscribers[type].push(fn)
  },
  unsubscribe: function(fn, type) {
    this.visitSubscribers('unsubscribe', fn, type)
  },
  publish: function(publication, type) {
    this.visitSubscribers('publish', publication, type)
  },
  visitSubscribers: function(action, arg, type = 'any') {
    this.subscribers[type].forEach((currentValue, index, array) => {
      if (action === 'publish') {
        currentValue(arg)
      } else if (action === 'unsubscribe') {
        if (currentValue === arg) {
          this.subscribers[type].splice(index, 1)
        }
      }
    })
  }
}

let funcA = function(cl) {
  console.log('msg1' + cl)
}
let funcB = function(cl) {
  console.log('msg2' + cl)
}

publisher.subscribe(funcA)
publisher.subscribe(funcB)
publisher.unsubscribe(funcB)

publisher.publish(' in publisher')     // msg1 in publisher

這裏能夠經過一個函數 makePublisher() 將一個對象複製成 publisher ,從而將其轉換成一個發佈者。設計模式

function makePublisher(o) {
  Object.keys(publisher).forEach((curr, index, array) => {
    if (publisher.hasOwnProperty(curr) && typeof publisher[curr] === 'function') {
      o[curr] = publisher[curr]
    }
  })
  o.subscribers={any:[]}
}

// 發行者對象
let paper = {
  daily: function() {
    this.publish('big news today')
  },
  monthly: function() {
    this.publish('interesting analysis', 'monthly')
  }
}

makePublisher(paper)

// 訂閱對象
let joe = {
  drinkCoffee: function(paper) {
    console.log('Just read daily ' + paper)
  },
  sundayPreNap: function(monthly) {
    console.log('Reading this monthly ' + monthly)
  }
}

paper.subscribe(joe.drinkCoffee)
paper.subscribe(joe.sundayPreNap, 'monthly')

paper.daily()         // Just read daily big news today
paper.monthly()         // Reading this monthly interesting analysis

3. 實現二

使用ES6裏的class稍微改造下:數組

class publisher {
    constructor() {
        this.subscribers = {
            any: []
        }
    }
    subscribe(fn, type = 'any') {
        if (typeof this.subscribers[type] === 'undefined') {
            this.subscribers[type] = []
        }
        this.subscribers[type].push(fn)
    }
    unsubscribe(fn, type) {
        this.visitSubscribers('unsubscribe', fn, type)
    }
    publish(publication, type) {
        this.visitSubscribers('publish', publication, type)
    }
    visitSubscribers(action, arg, type = 'any') {
        this.subscribers[type].forEach((currentValue, index, array) => {
            if (action === 'publish') {
                currentValue(arg)
            } else if (action === 'unsubscribe') {
                if (currentValue === arg) {
                    this.subscribers[type].splice(index, 1)
                }
            }
        })
    }
}

let publish = new publisher();

let funcA = function(cl) {
    console.log('msg1' + cl)
}
let funcB = function(cl) {
    console.log('msg2' + cl)
}

publish.subscribe(funcA)
publish.subscribe(funcB)
publish.unsubscribe(funcB)

publish.publish(' in publisher')     // msg1 in publisher

4. 實現三

以上兩個方法都是《JavaScript模式》裏介紹的,這裏貼上個本身實現的,感受看起來舒服點...緩存

  • 使用IIFE的方法:
const Observer = (function() {
  const _message = {}  // 消息隊列
  return {
    regist(type, fn) {          // 訂閱
      _message[type]
          ? _message[type].push(fn)
          : _message[type] = [fn]
    },
    emit(type, payload) {          // 發佈
      if (!_message[type]) {
        return
      }
      _message[type].forEach(event => event(payload))
    },
    remove(type, fn) {            // 退訂
      if (!_message[type].includes(fn)) {return}
      const idx = _message[type].indexOf(fn)
      _message[type].splice(idx, 1)
    }
  }
})()
  • 使用ES6的class方法
class Observer {
  constructor() {
    this._message = {}
  }
  
  regist(type, fn) {          // 訂閱
    this._message[type]
        ? this._message[type].push(fn)
        : this._message[type] = [fn]
  }
  
  emit(type, payload) {          // 發佈
    if (!this._message[type]) {
      return
    }
    this._message[type].forEach(event => event(payload))
  }
  
  remove(type, fn) {            // 退訂
    if (!this._message[type].includes(fn)) {return}
    const idx = this._message[type].indexOf(fn)
    this._message[type].splice(idx, 1)
  }
}

5. 總結

觀察者的使用場合就是:當一個對象的改變須要同時改變其它對象,而且它不知道具體有多少對象須要改變的時候,就應該考慮使用觀察者模式。微信

總的來講,觀察者模式所作的工做就是在解耦,讓耦合的雙方都依賴於抽象,而不是依賴於具體。從而使得各自的變化都不會影響到另外一邊的變化。函數


本文是系列文章,能夠相互參考印證,共同進步~學習

  1. JS 抽象工廠模式
  2. JS 工廠模式
  3. JS 建造者模式
  4. JS 原型模式
  5. JS 單例模式
  6. JS 回調模式
  7. JS 外觀模式
  8. JS 適配器模式
  9. JS 利用高階函數實現函數緩存(備忘模式)
  10. JS 狀態模式
  11. JS 橋接模式
  12. JS 觀察者模式

網上的帖子大多深淺不一,甚至有些先後矛盾,在下的文章都是學習過程當中的總結,若是發現錯誤,歡迎留言指出~this

參考:
設計模式之觀察者模式
《JavaScript模式》
《Javascript 設計模式》 - 張榮銘

PS:歡迎你們關注個人公衆號【前端下午茶】,一塊兒加油吧~

另外能夠加入「前端下午茶交流羣」微信羣,長按識別下面二維碼便可加我好友,備註加羣,我拉你入羣~

相關文章
相關標籤/搜索