观察者模式又称发布-订阅(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);
<!doctype html> <html> <head> <title>javascript 观察者模式 by 司徒正美</title> <meta http-equiv="content-type" content="text/html;charset=UTF-8" /> </head> <body> <script type="text/javascript"> 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); </script> </body> </html>
运行代码
这东西比较简单,涉及两个阵营。一个是发布者,你把它当成服务器端就是,肯定要做比客户端更多的事。为此,发布者要拥有订阅者的列表,支持添加或删除它们,当自己更新时,同步更新订阅者相应的东西。订阅者则不需要做许多事,只要提供一个update方法,供发布者调用就是。写这个时,我总是想起RSS,我的博客更新,便立即更新订阅者google reader上的内容。不过,javascript拥有DOM过门的事件系统,除非涉及的几个对象都不是元素节点,一般很少自己重新造轮子。
注:本文为rightjs学习笔记的一部分。