觀察者模式,定義對象間的一種一對多的依賴關係,當一個對象的狀態發生改變時,全部依賴於它的對象都將獲得通知。
事實上,只要你曾經在DOM節點上綁定過事件函數,那麼你就曾經使用過觀察者模式了!php
document.body.addEventListener('click', function () { alert(2); });
可是這只是對觀察者模式最簡單的使用,在不少場景下咱們常常會實現一些自定義事件來知足咱們的需求。前端
舉個例子: 你去一家公司應聘,談了一頓下來,hr跟你說:"好了,你回去等通知吧!"。 這個時候,1.你會問公司的電話,而後天天打過去問一遍結果 2.把本身的手機號留給hr,而後等他給你打電話 相信不少時候呢,你們都是選擇了後者。 萬一你天天給hr打電話弄煩他了,或許他原本打算招你的,如今也再也不打算再鳥你啦! 那麼這個時候,hr就至關於一個發佈者,而你就是一個訂閱者啦! 好吧,大部分叫你回去等消息的就等於沒救啦...... 我還遇到過一個若是你沒被錄取,就連通知都不通知你的公司!
那麼一個簡單的觀察者模式應該怎麼實現呢?web
var event = {}; //發佈者(hr) event.clietList = []; //發佈者的緩存列表(應聘者列表) event.listen = function(fn) { //增長訂閱者函數 this.clietList.push(fn); }; event.trigger = function() { //發佈消息函數 for (var i = 0; i < this.clietList.length; i++) { var fn = this.clietList[i]; fn.apply(this, arguments); } }; event.listen(function(time) { //某人訂閱了這個消息 console.log('正式上班時間:' + time); }); event.trigger('2016/10',yes); //發佈消息 //輸出 正式上班時間:2016/10
到這裏,咱們已經實現了一個最簡單的觀察者模式了!緩存
可是上面的函數其實存在一個問題,那就是發佈者沒辦法選擇本身要發佈的消息類型!
好比這家公司同時在招php,web前端,若是使用上面的函數就沒辦法區分職位了!只能一次性把所有訂閱者都發送一遍消息。
對上面的代碼進行改寫:閉包
var event = {}; //發佈者(hr) event.clietList = []; //發佈者的緩存列表(應聘者列表) event.listen = function(key, fn) { //增長訂閱者函數 if (!this.clietList[key]) { this.clietList[key] = []; } this.clietList[key].push(fn); }; event.trigger = function() { //發佈消息函數 var key = Array.prototype.shift.call(arguments), fns = this.clietList[key]; for (var i = 0; i < fns.length; i++) { var fn = fns[i]; fn.apply(this, arguments); } }; event.listen('web前端', fn1 = function(time) { //小強訂閱了這個消息。 console.log('姓名:小強'); console.log('正式上班時間:' + time); }); event.listen('web前端', fn2 = function(time) { //大大強訂閱了這個消息 console.log('姓名:大大強'); console.log('正式上班時間:' + time); }); //發佈者發佈消息 event.trigger('web前端','小強', '2016/10'); //姓名:小強 正式上班時間:2016/10 event.trigger('php','大大強', '2016/15'); //姓名:大大強 正式上班時間:2016/15
經過添加了一個key,咱們實現了對職位的判斷。app
有了訂閱事件,咱們怎麼能少了取消訂閱事件呢?函數
event.remove = function(key, fn) { var fns = this.clietList[key]; if (!fns) { return false; } if (!fn) { //若是沒有傳入fn回調函數,直接取消key對應消息的全部訂閱 this.clietList[key] = []; } else { for (var i = 0; i < fns.length; i++) { //遍歷回調函數列表 var _fn = fns[i]; if (_fn === fn) { fns.splice(i, 1); //刪除訂閱者的回調函數 } } } }; //這時候必須指定回調函數,不然沒法在remove函數中進行對比刪除。 event.listen('web前端', fn1 = function(time) { //小強訂閱了這個消息。 console.log('姓名:小強'); console.log('正式上班時間:' + time); }); event.listen('web前端', fn2 = function(time) { //大大強訂閱了這個消息 console.log('姓名:大大強'); console.log('正式上班時間:' + time); }); event.remove('web前端',fn1); //發佈者發佈消息 event.trigger('web前端','2016/10'); //輸出 姓名:大大強 正式上班時間:2016/10
對上面代碼進行改進,建立一個全局對象來實現觀察者模式,
使用閉包實現私有變量,僅暴露必須的API給使用者:this
var event = (function() { var clietList = []; //發佈者的緩存列表(應聘者列表) var listen = function(key, fn) { //增長訂閱者函數 if (!this.clietList[key]) { this.clietList[key] = []; } this.clietList[key].push(fn); }; var trigger = function() { //發佈消息函數 var key = Array.prototype.shift.call(arguments), fns = this.clietList[key]; for (var i = 0; i < fns.length; i++) { var fn = fns[i]; fn.apply(this, arguments); } }; var remove = function(key, fn) { var fns = this.clietList[key]; if (!fns) { return false; } if (!fn) { //若是沒有傳入fn回調函數,直接取消key對應消息的全部訂閱 this.clietList[key] = []; } else { for (var i = 0; i < fns.length; i++) { //遍歷回調函數列表 var _fn = fns[i]; if (_fn === fn) { fns.splice(i, 1); //刪除訂閱者的回調函數 } } } }; return{ listen:listen, trigger:trigger, remove:remove } })();
觀察者模式進階:prototype