觀察者模式又稱發布-訂閱(Publish/Subscribe)模式、模型-視圖(Model/View)模式、源-監聽器(Source/Listener)模式或從屬者(Dependents)模式。
觀察者模式定義了一種一對多的依賴關係,讓多個觀察者對象同時監聽一個主題對象。這個主題對象在狀態上發生變化時,會通知所有觀察者,使它們能夠自動更新自己。
下面拿老外的例子改一改,練練手。
var dom = { each : function(obj,fn,score){ for(var key in obj){ if(obj.hasOwnProperty(key)) fn.call(score,obj[key],key,obj) } } }; dom.each({ indexOf: function (el, index) { var n = this.length, i = index == null ? 0 : index < 0 ? Math.max(0, n + index) : index; for (; i < n; i++) if (i in this && this[i] === el) return i; return -1; }, //移除 Array 對象中指定位置的元素。 removeAt: function (index) { return this.splice(index, 1) }, //移除 Array 對象中某個元素的第一個匹配項。 remove: function (item) { var index = this.indexOf(item); if (index !== -1) this.removeAt(index); return item; } },function(method,name){ if(!Array.prototype[name]) Array.prototype[name] = method; }); /* 訂閱者介面 */ var Observer = function() { //觀察者要實現的方法 this.update = function() {throw "此方法必須被實現!"} } /* 發行者介面 */ var Subject = function() { this.observers = []; } Subject.prototype = { //如果狀態發生改變,通知所有觀察者調用其update方法 notifyObservers : function(context) { for(var i = 0, n = this.observers.length; i < n; i++) { this.observers[i].update(context); } }, // 添加訂閱者 attach : function(observer){ if(!observer.update) throw 'Wrong observer'; this.observers.push(observer); }, /* 移除訂閱者 */ detach : function(observer) { if(!observer.update) { throw 'Wrong observer'; } this.observers.remove(observer); } } //實現介面 var implement = function(Concrete,Interface){ for(var prop in Interface) { Concrete[prop] = Interface[prop]; } } /***************** 發行者的實作類別 ***********************/ var mainCheck = document.createElement("input"); mainCheck.type = 'checkbox'; mainCheck.id = 'MainCheck'; mainCheck.style.cssText = 'border:1px solid red'; implement( mainCheck,new Subject()); /* 當點擊按鈕的時候 給相關的觀察者發送通知. 觀察者接收到通知的時候 改變狀態 */ mainCheck['onclick'] = function(){ this.notifyObservers(this.checked) } document.body.appendChild(mainCheck); /********************* 訂閱者的實作類別 *****************************/ var obsCheck1 = document.createElement('input'); var obsCheck2 = document.createElement('input'); obsCheck1.type = 'checkbox'; obsCheck1.id = 'Obs1'; document.body.appendChild(obsCheck1); obsCheck2.type = 'checkbox'; obsCheck2.id = 'Obs2'; document.body.appendChild(obsCheck2); implement( obsCheck1,new Observer()); implement( obsCheck2,new Observer()); /* 必須實現它們的具體update方法 */ obsCheck1.update = function(value) { this.checked = value; } obsCheck2.update = function(value) { this.checked = value; } // 將發行者和訂閱者(觀察者)關聯 mainCheck.attach(obsCheck1); mainCheck.attach(obsCheck2);
<br /><!doctype html><br /><html><br /> <head><br /> <title>javascript 觀察者模式 by 司徒正美</title><br /> <meta http-equiv="content-type" content="text/html;charset=UTF-8" /><br /> </head><br /> <body><br /> <script type="text/javascript"><br /> var dom = {<br /> each : function(obj,fn,score){<br /> for(var key in obj){<br /> if(obj.hasOwnProperty(key))<br /> fn.call(score,obj[key],key,obj)<br /> }<br /> }<br /> };<br /> dom.each({<br /> indexOf: function (el, index) {<br /> var n = this.length,<br /> i = index == null ? 0 : index < 0 ? Math.max(0, n + index) : index;<br /> for (; i < n; i++)<br /> if (i in this && this[i] === el) return i;<br /> return -1;<br /> },<br /> //移除 Array 對象中指定位置的元素。<br /> removeAt: function (index) {<br /> return this.splice(index, 1)<br /> },<br /> //移除 Array 對象中某個元素的第一個匹配項。<br /> remove: function (item) {<br /> var index = this.indexOf(item);<br /> if (index !== -1) this.removeAt(index);<br /> return item;<br /> }<br /> },function(method,name){<br /> if(!Array.prototype[name])<br /> Array.prototype[name] = method;<br /> });</p><p> /* 訂閱者介面 */<br /> var Observer = function() {<br /> //觀察者要實現的方法<br /> this.update = function() {throw "此方法必須被實現!"}<br /> }<br /> /* 發行者介面 */<br /> var Subject = function() {<br /> this.observers = [];<br /> }<br /> Subject.prototype = {<br /> //如果狀態發生改變,通知所有觀察者調用其update方法<br /> notifyObservers : function(context) {<br /> for(var i = 0, n = this.observers.length; i < n; i++) {<br /> this.observers[i].update(context);<br /> }<br /> },<br /> // 添加訂閱者<br /> attach : function(observer){<br /> if(!observer.update) throw 'Wrong observer';<br /> this.observers.push(observer);<br /> },<br /> /* 移除訂閱者 */<br /> detach : function(observer) {<br /> if(!observer.update) { throw 'Wrong observer'; }<br /> this.observers.remove(observer);<br /> }<br /> }<br /> //實現介面<br /> var implement = function(Concrete,Interface){<br /> for(var prop in Interface) {<br /> Concrete[prop] = Interface[prop];<br /> }<br /> }</p><p> /***************** 發行者的實作類別 ***********************/<br /> var mainCheck = document.createElement("input");<br /> mainCheck.type = 'checkbox';<br /> mainCheck.id = 'MainCheck';<br /> mainCheck.style.cssText = 'border:1px solid red';<br /> implement( mainCheck,new Subject());<br /> /* 當點擊按鈕的時候 給相關的觀察者發送通知. 觀察者接收到通知的時候 改變狀態 */<br /> mainCheck['onclick'] = function(){<br /> this.notifyObservers(this.checked)<br /> }</p><p> document.body.appendChild(mainCheck);</p><p> /********************* 訂閱者的實作類別 *****************************/<br /> var obsCheck1 = document.createElement('input');<br /> var obsCheck2 = document.createElement('input');<br /> obsCheck1.type = 'checkbox';<br /> obsCheck1.id = 'Obs1';<br /> document.body.appendChild(obsCheck1);</p><p> obsCheck2.type = 'checkbox';<br /> obsCheck2.id = 'Obs2';<br /> document.body.appendChild(obsCheck2);<br /> implement( obsCheck1,new Observer());<br /> implement( obsCheck2,new Observer());</p><p> /* 必須實現它們的具體update方法 */<br /> obsCheck1.update = function(value) {<br /> this.checked = value;<br /> }</p><p> obsCheck2.update = function(value) {<br /> this.checked = value;<br /> }</p><p> // 將發行者和訂閱者(觀察者)關聯<br /> mainCheck.attach(obsCheck1);<br /> mainCheck.attach(obsCheck2);</p><p> </script><br /> </body><br /></html><br />
運行代碼
這東西比較簡單,涉及兩個陣營。一個是發行者,你把它當成伺服器端就是,肯定要做比用戶端更多的事。為此,發行者要擁有訂閱者的列表,支援添加或刪除它們,當自己更新時,同步更新訂閱者相應的東西。訂閱者則不需要做許多事,只要提供一個update方法,供發行者調用就是。寫這個時,我總是想起RSS,我的部落格更新,便立即更新訂閱者google reader上的內容。不過,javascript擁有DOM過門的事件系統,除非涉及的幾個對象都不是元素節點,一般很少自己重新造輪子。
註:本文為rightjs學習筆記的一部分。