javascript觀察者模式
介紹
觀察者模式又稱發佈-訂閱模式
,它定義對象間的一種一對多的依賴關係,當一個對象發生改變的時候,所依賴它的對象都能得到通知。例如:我們訂閱了一個欄目,當欄目有新文章的時候,它會自動通知所有訂閱它的人。
特點
- 發佈 & 訂閱
- 一對多
優點
- 低耦合,觀察者和觀察目標都是抽象出來,容易擴展和重用
- 觸發(通訊)機制,由觀察目標通知所有觀察它的人
缺點
- 一個觀察目標下可能存在很多的觀察者,那麼當觀察目標需要通知所有觀察者的時候會花很多時間
- 觀察者和觀察目標之間如果存在依賴的話,可能會發生循環調用,進入死循環導致系統崩潰
- 觀察者模式沒有相應的機制讓觀察者知道觀察目標是如何發生變化,僅僅知道觀察目標發生了變化
- 觀察目標可能將一些無用的更新發送出去
簡單例子
- 例子:A,B,C三個人都關注了某一個電臺,當電臺發佈新內容的時候通知A,B,C三個人。
- 構思:具體的構思中,我們可以知道電臺作爲發佈者,它有了新的內容,需要向ABC這三個訂閱者推送他的最新的消息。那麼我們就可以知道電臺這個發佈者需要包含新的更新內容以及有哪些訂閱者訂閱了它。
-
代碼實現:
簡單的代碼實現:class Radio { constructor() { this.state = 0; this.observers = []; } setState(state) { this.state = state; this.notifyingObservers(); } addObserver(observer) { this.observers.push(observer); } notifyingObservers() { const state = this.state; this.observers.forEach(observer => { observer.update(state); }); } } class People { constructor(name) { this.name = name; } update(content) { console.log(`我是${this.name},我接受到更新了,更新內容:${content}`); } } // 創建訂閱者 const peopleA = new People('小A'); const peopleB = new People('小B'); const peopleC = new People('小C'); // 添加發布者 const radio = new Radio(); // 訂閱 radio.addObserver(peopleA); radio.addObserver(peopleB); radio.addObserver(peopleC); // 發佈者發佈 radio.setState('十月份最熱歌單發佈了'); radio.setState('十一月份最新原創歌單發佈了');
- 解讀:
- 抽象了一個發佈者(
Radio
),它有更新內容(setState
)、添加訂閱者(addObserver
)、以及觸發所有訂閱者(notifyingObservers
); - 抽象了一個訂閱者(
People
),他有自己的個人信息(如:name
),以及接受到通知後所需要執行的動作(updata
); - 當我們每次更新消息的時候出發
notifyingObservers
方法,將所有的observers
的update
都觸發了
當然,實際上,我們上的每一個訂閱者都有這個update,當這個update不滿足功能需求的時候,我們同樣可以將實例出來的訂閱者單獨設置update; 如:
peopleA.update = function(content) {
// 新代碼
}
以上就是一個簡單的觀察者模式的例子
場景延伸
- 網頁頁面事件
-
代碼案例:
<button id="btn">點擊</button> <script> $('#btn').click(function() { console.log('btn被點擊'); }) </script>
- 解讀:可以理解成函數訂閱了
$('#btn')
的click事件,當$('#btn')
的click被我們點擊觸發,函數收到觸發信息,並自執行。 那麼這個函數就是觀察者(訂閱者),$('#btn')
的click事件就是觀察目標(發佈者)。
- Promise
-
代碼案例:
function loadImage(url) { return new Promise(function(resolve, reject) { let image = document.createElement('img'); image.onload = function () { resolve(image); } image.onerror = function () { reject('圖片加載失敗'); } image.src = url; }); } const src = 'http://imgsrc.baidu.com/image/c0%3Dpixel_huitu%2C0%2C0%2C294%2C40/sign=ad13ee4af0f2b211f0238d0ea3f80054/2e2eb9389b504fc26849383ceedde71190ef6df1.jpg' const img = loadImage(src); img.then(function (img) { console.log('width', img.width); return img }).then(function (img) { console.log('height', img.height); });
- 解讀:promise的resolve是then的執行者,當promise的狀態發生改變後(resolve的時候狀態從”未完成“變爲”成功“),一一執行then下的方法,那麼這些then可以說是promise的觀察者,當這個promise被resolve的時候,所有的觀察得到了通知。
- 關於promise內部的觀察者模式可以參數
https://github.com/xieranmaya/blog/issues/3
這篇文檔。
promise.then會把內部的函數添加到一個callback的數組內,等異步執行完成之後在進行一次調用該函數。每一個.then會返回一個新的promise。
- js的事件觸發器(自定義事件)
-
代碼案例:
class EventEmitter() { constructor() { this.events = {}; } // 訂閱事件 on(type, listener) { if (!this.events) { this.events = Object.create(null); } if (this.events[type]) { this.events[type].push(listener) } else { this.events[type] = [listener]; } } // 觸發執行 emit(type, ...args) { if (this.events[type]) { this.events[type].forEach(fn => fn.call(this, ...args)); } } // 解綁 off(type, listener) { id (this.events[type]) { this.events[type] = this.events[type].filter(fn => { return fn !== listener; }); } } } const myEmitter = new EventEmitter(); myEmitter.on('log', function() {console.log('111111')}); myEmitter.emit('log');
-
解讀:這個就和第一個案例有點相似,我們在jq中見過這樣的頁面事件寫法:
$('id').on('click', function() { // 事件代碼 });
這個就是一種事件觸發器,在nodejs裏面大量採用了事件觸發器的方法。詳情可以去看nodejs裏面的
EventEmitter
方法,就可以大體理解他的機制了。
同理,我們也可以明白react裏面的生命週期等mvvm框架,裏面大量採用了觀察者模式。它們都是定義了一個個鉤子,等狀態達到的時候我就觸發相對應的鉤子,執行相對應的代碼。
總結
總之,觀察者模式在javascript中的使用是非常廣泛的。其低耦合的特點方便在多人開發的複雜項目中,能提高效率,使代碼的維護性大大提升。