在事件驱动的环境中,比如浏览器这种持续寻求用户关注的环境中,观察者模式是一种管理人与其任务(确切的讲,是对象及其行为和状态之间的关系)之间的关系的得力工具。用javascript的话来讲,这种模式的实质就是你可以对程序中某个对象的状态进行观察,并且在其发生变化时可以得到通知。

观察者模式中存在俩个角色:观察者和被观察者。也可以叫做发布者和订阅者。这种模式在javascript中有几种不同的实现方法,下面将对其中的一些实现方式进行考察。

模式的实践:

在javascript中有多种方法可以实现发布者-订阅者模式,在展示这些示例之前,我们先确保各种角色的扮演者(对象)及其行为(方法)都已经就绪。

  • 订阅者可以订阅和退订。他们还要接收。他们可以在“由人投送”和“自己收取”之间进行选择。
  • 发布者负责投送。他们可以在“送出”和“由人取”之间进行选择。

下面是一个发布者和订阅者之间的互动过程的高层示例。

var Publisher = new Observable;
var Subscriber = function (news) {
//news delivered directly to my proch;
}
Publisher.subscribeCustomer(Subscriber);
Publisher.deliver('extre,extre,read all about it!');
Publisher.unSubscribeCustomer(Subscriber);

在这个模型中可以看出,发布者处于明显的主导。他们负责登记其客户,而且有权停止投送。最后新的报纸出版后它们会将其投送给客户。

上面的代码创建了一个新的可观察对象(Observable)。它有三个实例方法:subscribeCustomer、unSubscribeCustomer和 deliver。subscribeCustomer方法以一个订阅者的回调函数为参数。deliver方法在调用过程中将通过这些回调函数把数据发送给每一个订阅者。

下面的例子处理同一类问题,但是发布者和订阅者之间的互动方式有所不同。

var newYorkTime = new Publisher;
var AustinHerald = new Publisher;
var SfChronicle = new Publisher; var Jon = function (from) {
console.log('Delivery from'+from+'to jon');
}
var Lindsay = function (from) {
console.log('Delivery from'+from+'to Lindsay');
}
var Quadaras = function (from) {
console.log('Delivery from'+from+'to Quadaras');
} jon.
subscribe(newYorkTime).
subscribe(SfChronicle); Lindsay.
subscribe(newYorkTime).
subscribe(AustinHerald).
subscribe(SfChronicle); Lindsay.
subscribe(newYorkTime).
subscribe(SfChronicle); newYorkTime.deliver('Here is your paper!');
AustinHerald.deliver('News').deliver('REviews').deliver('Coupons');
SfChronicle.deliver('the weather is sill chilly').deliver('hello..');

在这个例子中,发布者的创建方式和订阅者接收数据的方式没有多少改变,但是用于订阅和退订权的一方变成了订阅者,当然,负责发送数据的还是发布者一方。

本类中的发布者是 Publisher 的实例。他有一个deliver方法。而作为订阅者的函数对象则拥有subscribe和unsubscribe俩个方法。订阅者只是普通的回调函数,这俩个方法是通过扩展Function的prototype而加入的,下面我们将一步一步的构建符合需要的API。

构建观察者API:

function Publisher(){
this.subscribe = [];
}

所有Publisher实例都应该能够投送数据。只要把deliver方法添加到Publisher的prototype中,他就能被所有Publisher对象共享:

Publisher.prototype.deliver = function (data) {
this.subscribes.forEach(
function (fn) {
fn(data);
}
);
return this;
}

这个方法使用的是javascript 1.6中新加的数组方法forEach逐一处理每一个订阅者。forEach方法会对一个“草垛”(haystack)从头到尾访问一遍,把每一根针,针的索引和整个数组提供给一个回调方法。订阅者数组中的每根针都是一个回调函数,比如Joe,Lindsay,Quadaras。

deliver 方法把this用作返回值,因此可以对该方法进行链式调用。

下一步是给以订阅者订阅的能力。

Function.prototype.subscribe = function (publisher) {
var _this = this;
var alreadyExists = publisher.subscribes.some(
function (el) {
return el===_this;
}
);
if (!alreadyExists) {
publisher.subscribes.push(this);
}
return this;
}

这段代码为Function的prototype添加了一个以Publisher对象为参数的subscribe方法。所有函数都能调用这个方法。subscribe方法先定义了一个this变量,并把this赋给它。后面用作数组的some方法参数的那个匿名函数将通过闭包机制访问到这个变量,从而访问到用以调用subscribe方法的那个函数对象。

some 也是javascript 1.6中新增加的数组方法,它以一个回调函数为参数,some逐一访问数组的各个元素,并以其为参数调用那个回调函数,只要至少有一次调用函数时返回true,则some方法返回ture,否则some方法返回false。subscribe把some方法的返回值赋值给alreadyExists,然后根据这个变量的值决定是否为指定的发布者添加一个订阅者。最后subscribe方法返回this,支持链式调用。

unsubscribe方法可供订阅者用来停止对事件发布者的观察:

Function.prototype.unsubscribe = function (publisher) {
var _this = this;
publisher.subscribes = publisher.subscribes.filter(
function (el) {
return el !== _this;
}
);
return this;
}

有订阅者在监听到某种一次性的事件之后会在回调阶段立即退订该事件。其做法大致如下:

var publisherObject = new Publisher();
var observerObject = function (data) {
//process data
console.log(data);
//unsubscribe from this publisher
arguments.callee.unsubscribe(publisherObject);
}
observerObject.subscribe(publisherObject);

在现实世界中,观察者模式对于那种由许多javascript程序员合作开发的大型程序特别有用。它可以提高API的灵活性,使并行开发的多个实现能够彼此独立地进行修改。作为开发人员,你可以对自己的应用程序中什么是“令人感兴趣的时刻”做出决定。你所监听的不再是click、load、blur和mouseover等浏览器事件,在富用户界面应用程序中,drag,dropmoved,complete和tabSwitch都可能是令人感兴趣的事件。他们都是在普通浏览器事件的基础上抽象出来的可观察事件,可由发布者对象向其监听者广播。

实例动画:

动画是在应用程序中实现可观察对象的一个很好的起点。眨眼之间你就可以想出至少3个可观察的时刻:开始、结束和进行中。在本例中,我们将分别称之为onStart、onComplete、onTween。下面的代码演示了用前面编写的Publisher工具实现这些事件的过程。

var Animation = function (o) {
this.onStart = new Publisher,
this.onComplete = new Publisher,
this.onTween = new Publisher;
} Animation.method('fly', function () {
//begin animation
this.onStart.deliver();
for (...) { //loop through frames
//deliver frame number
this.onTween.deliver(i);
}
//end animation
this.onComplete.deliver();
}); //setup an account with the animation manager
var Superman = new Animation({config}); //Begin implementing subscribers
var putOnCape = function (i) {}
var takeOffCape = function (i) {} putOnCape.subscribe(Superman.onStart);
takeOffCape.subscribe(Superman.onComplete); //fly can be called anywhere
Superman.fly();
//for instance:
addEvent(element, 'click', function () {
Superman.fly();
});

可以看到,如果你是负责实现为超人披上斗篷和解下斗篷的功能的人的话,这种运作方式还真不错。借助于发布者,你可以知道超人什么时候起飞以及什么时候回到地面,你只需预定这些时刻的通知便万事大吉了。

在DOM脚本编程环境中的高级事件模式中,事件监听器说到底是一种内置的观察者。事件处理器handler与事件监听器listener并不是一回事。前者说穿了就是一种把事件传给与其相关联的函数的手段,而且在这种模型中一种事件只能制定一个回调方法。而在监听器模式中,一个事件可以与几个监听器关联。每个监听器都能独立于其他监听器而改变。打个比方,对SanFrancisco Chronicle这家报社来说 ,其订阅者joe定没有定New York Times都无所谓。同样,joe不在在乎Lindsay是否也订阅了该报纸。每一方都只管处理自己的数据和相关行为。

例如,使用事件监听器,可以让多个函数响应同一个事件:

var el = $('example');
var fn1 = function(e){}
var fn2 = function(e){}
addEvent(el,'click',fn1)
addEvent(el,'click',fn2)

但是事件处理器就办不到

var el = $('example');
var fn1 = function(e){}
var fn2 = function(e){}
el.onclick = fn1;
el.onclick = fn2;

第一个例子中,使用事件监听器,所以fn1和fn2都会被调用。而第二个例子则第二次对onclick赋值会覆盖掉第一次,只会调用fn2。

言归正传,监听器和观察者之间的共同之处显而易见,实际上他们互为同义语。他们都订阅特定的事件,然后等待事件的发生,事件发生的时候,订阅方的回调函数会得到通知。传给他们的参数是一个事件对象,其中包含着事件发生时间,事件类型和事件发源地等有用的信息。

观察者模式适用场合:

如果希望把人的行为和应用程序的行为分开,那么观察者模式正适合这种场合。最好不要实现一些与用户操作绑定在一起而且来源于浏览器的东西,比如 click,keypress之类的基本DOM事件。对于那些只关心动画的开始,或者发现错别字的程序员而言,那些事件提供不了什么有用信息。

举例来说,用户点击导航的一个标签tab时,会打开一个包含更多信息的菜单。当然你可以直接监听这个click,不过这需要知道监听的是哪个元素,这样做的另一个弊端是你的实现与click事件直接绑在了一起。比监听click更好的做法是:创建一个可观察的onTabChange对象。并且在特定事件发生时通知所有观察者。如果菜单改为在鼠标指向标签时或者标签处于焦点之下时打开,那么这个onTabChange对象会替你处理这种改变。

观察者模式是开发基于行为的大型应用程序的有力手段。在一次浏览器会话期间,应用程序中可能会断断续续发生几十次事件,你可以削减为事件注册监听器的次数,让可观察对象借助一个事件监听器替你处理各种行为并将信息委托delegate给她的所有订阅者。从而降低内存消耗和提高性能。这样一来,就不用没完没了地为同样的元素添加新的事件监听器。

创建可观察对象会带来加载时间的开销。但是这可以采用惰性加载技术化解。具体来说就是把新的可观察对象的实例化推迟到需要发送事件通知的时候,这样一来,订阅者在事件尚未创建的时候就能订阅它,应用程序的初始加载时间也就不会受到影响。

读书笔记之 - javascript 设计模式 - 观察者模式的更多相关文章

  1. 读书笔记之 - javascript 设计模式 - 接口、封装和链式调用

    javascript 采用设计模式主要有下面的三方面原因: 可维护性:设计模式有助于降低模块之间的耦合程度.这使代码进行重构和换用不同的模块变得容易,也使程序员在大型项目中合作变得容易. 沟通:设计模 ...

  2. 读书笔记之 - javascript 设计模式 - 命令模式

    本章研究的是一种封装方法调用的方式.命令模式与普通函数有所不同.它可以用来对方法调用进行参数化处理和传送,经过这样处理过的方法调用可以在任何需要的时候执行. 它也可以用来消除调用操作的对象和实现操作的 ...

  3. 读书笔记之 - javascript 设计模式 - 代理模式

    代理(proxy)是一个对象,它可以用来控制对另一对象的访问.它与另外那个对象实现了同样的接口,并且会把任何方法调用传递给那个对象.另外那个对象通常称为本体.代理可以代替本体被实例化,并使其可被远程访 ...

  4. 读书笔记之 - javascript 设计模式 - 享元模式

    本章探讨另一种优化模式-享元模式,它最适合于解决因创建大量类似对象而累及性能的问题.这种模式在javascript中尤其有用,因为复杂的javascript代码很快就会用光浏览器的所有可用内存,通过把 ...

  5. 读书笔记之 - javascript 设计模式 - 门面模式

    门面模式有俩个作用: 简化类的接口 消除类与使用它的客户代码之间的耦合 在javascript中,门面模式常常是开发人员最亲密的朋友.它是几乎所有javascript库的核心原则,门面模式可以使库提供 ...

  6. 读书笔记之 - javascript 设计模式 - 单体模式

    单体是一个用来划分命名空间,并将一批相关方法和属性组织在一起的对象,如果它可以被实例化,那么它只能被实例化一次. 单体模式,就是将代码组织为一个逻辑单元,这个逻辑单元中的代码可以通过单一的变量进行访问 ...

  7. 读书笔记之 - javascript 设计模式 - 组合模式

    组合模式是一种专为创建Web上的动态用户界面而量身定制的模式,使用这种模式,可以用一条命令在对各对象上激发复杂的或递归的行为. 在组合对象的层次体系中有俩种类型对象:叶对象和组合对象.这是一个递归定义 ...

  8. 读书笔记之 - javascript 设计模式 - 工厂模式

    一个类或者对象中,往往会包含别的对象.在创建这种对象的时候,你可能习惯于使用常规方式,即用 new 关键字和类构造函数. 这会导致相关的俩个类之间产生依赖. 工厂模式,就是消除这俩个类之间的依赖性的一 ...

  9. 读书笔记之 - javascript 设计模式 - 责任链模式

    责任链模式可以用来消除请求的发送者和接收者之间的耦合.这是通过实现一个由隐式地对请求进行处理的对象组成的链而做到的.链中的每个对象可以处理请求,也可以将其传给下一个对象. 责任链的结构: 责任链由多个 ...

随机推荐

  1. JavaScript高级程序设计56.pdf

    选择选项 对于只允许选择一项的选择框,访问选中项最简单的方式是selectedIndex属性 var selectedOption=selectbox.options["selectbox. ...

  2. How to compile pycrypto 2.4.1 (python 3.2.2 for Windows 7 x64)

    How to compile pycrypto 2.4.1 (python 3.2.2 for Windows 7 x64) Nov 10 Posted by alesk This note is a ...

  3. 28个Unix/Linux的命令行神器

    下面是Kristóf Kovács收集的28个Unix/Linux下的28个命令行下的工具(原文链接),有一些是大家熟悉的,有一些是非常有用的,有一些是不为人知的.这些工具都非常不错,希望每个人都知道 ...

  4. JAVA Web项目的编码过滤器

    首先写一个EncodeFilter的过滤类: package com.djtu.wy.common; import java.io.IOException;import javax.servlet.F ...

  5. mongDB基本命令和Java操作MongoDB

    上一篇博文<mongoDB安装>我们安装了mongoDB,现在来复习一下它的一些基本命令:mongoDB的bin目录加入到path之后,命令行中输入mongo: 然后我们进入正题 1.查看 ...

  6. div border-radius画圆

    不要指定border-width属性: <html> <style type="text/css"> div{ border-style: solid; b ...

  7. Android学习路线图

    第一阶段:Java面向对象编程 1.Java基本数据类型与表达式,分支循环. 2.String和StringBuffer的使用.正则表达式. 3.面向对象的抽象,封装,继承,多态,类与对象,对象初始化 ...

  8. 用CS-Script把Notepad++变身支持智能提示和运行代码的C#集成开发环境

    博客搬到了fresky.github.io - Dawei XU,请各位看官挪步.最新的一篇是:用CS-Script把Notepad++变身支持智能提示和运行代码的C#集成开发环境.

  9. angular js 表单验证

    <!doctype html> <html ng-app="myapp"> <head> <meta charset="UTF- ...

  10. [React] React Fundamentals: Precompile JSX

    The JSX Transformer library is not recommended for production use. Instead, you'll probably want to ...