本节稍稍深入地讨论关于事件处理的话题,如果你对模式、闭包和面向对象等概念还不太理解,不妨暂且等阅读完相关内容之后再回过头来阅读它,相信你会有很大收获。
1 事件处理模式
在程序设计领域,“事件处理”是一种模式,当一个对象受外部影响而改变状态时,通过消息的方式将这个状态改变通知给这个对象或者相关联的某个对象,让它执行对应的动作,这就是事件处理的基本原理。负责通知状态改变的对象被称作“消息”,而执行响应动作的属性则被称作“事件代理”。
例如下面就是一个简单的事件处理模式的应用:
function dispatchEvent(owner, eventType, eventArgs)
{
if(owner && owner["on"+eventType])
setTimeout(function(){owner["on"+eventType](eventArgs)}, 1);
}
function randomSerials(len)
{
function randomSignal()
{
return Math.random() > 0.5 ? 1 : 0;
}
var ret = [];
for(var i = 0; i {
ret.push(randomSignal());
}
return ret;
}
function Differ(obl)
{
var buffer = new Array(obl);
var time = 0;
this.readBuffer = function()
{
var buf = buffer;
buffer = new Array(obl);
time = 0;
return buf;
}
this.bufferSize = function()
{
return obl;
}
this.input = function(serials)
{
for(var i = 1; i {
var signal = Math.abs(serials[i] - serials[i - 1]);
buffer[time++ % obl] = signal;
if(signal)
dispatchEvent(this, "signalchange",
{input:serials, time:time, buffer:buffer.slice(0)});
}
}
}
var inputSerials = randomSerials(20);
alert(inputSerials);
var diff10 = new Differ(20);
diff10.input(inputSerials);
alert(diff10.readBuffer());
diff10.onsignalchange = function(eventArgs)
{
alert(eventArgs.time);
}
diff10.input(inputSerials);
在上面的例子中,函数dispatchEvent负责分派事件,onsignalchange是事件代理,在这个差分系统diff10中,当输入信号的电平发生变化(从0到1或者从1到0)时,触发相应的事件onsignalchange,并且将当前输入信号、时序和当前输出缓存作为事件参数传入事件处理程序。
diff10.onsignalchange = function(eventArgs)
{
alert(eventArgs.time);
}
是程序员指定的事件处理程序,在这里我们打印出输入电平发生变化时的输入信号时序。
2 用户事件接口的定义
前面的例子中,我们仅仅定义了一个用来分派事件的函数dispatchEvent,但它也可以看作是一个完整的用户事件接口,现在我们回顾这个函数,弄明白它究竟做了什么样的事情:
function dispatchEvent(owner, eventName, eventArgs)
{
if(owner && owner["on"+eventName])
setTimeout(function(){owner["on"+eventName](eventArgs)}, 1);
}
这个函数接收三个参数,它的第一个参数是一个对象,指定了这个事件的“所有者”,即这个事件是由谁接收和负责处理的。在上面的例子中,这个owner是Differ对象本身即
dispatchEvent(this, "signalchange", {input:serials, time:time, buffer:buffer});
传入的owner参数是this,实际上事件模式允许其他类型作为事件分派的所有者,尤其在一些特定的模式,通常事件的发起者和事件的接收者可以不是同一个对象。在4小节介绍的观察者模式中可以看到这一点。
第二个参数是一个表示事件类型的字符串,它决定了事件代理的名称,根据事件模型的规范,事件代理的名称为”on”+事件类型,例如上面例子中,事件类型为signalchange,对应的事件代理为onsignalchange。
第三个参数是一个事件参数对象,它决定了传递给事件接收者的参数,在上面的例子中,它传递了input、time和buffer三个属性,分别代表发生事件时的当前输入序列、时序以及输出缓存的值。
dispatchEvent函数本身的内容很简单,它只是确保调用接收者的事件代理,并将事件参数正确传入这个事件代理。至于事件代理是如何处理事件参数的,它并不关心。
3 事件代理和事件注册
在事件处理模式中,为事件代理指定事件处理函数的过程被称为事件注册。在上面的例子中,diff10.onsignalchange是极其简单的事件代理,它的事件注册过程也极为简单——采用直接赋值的方式来完成。
事实上根据设计的不同,事件代理可以有更加复杂的注册方式,例如DOM-level-2的addEventListener和removeEventListener,我们也可以实现类似的事件注册方法,以支持为一个事件代理注册多个事件事件处理方法。为了实现它,我们完善事件接口,修改上面的例子如下:
function EventManager(owner)
{
owner = owner || this;
this.dispatchEvent = function(eventType, eventArgs)
{
var events = owner["on"+eventType];
if(events && typeof(events) == "function")
events = [events];
if(owner && events)
{
for(var i = 0; i {
setTimeout(
(function(i){return function(){events[i](eventArgs)}
})(i), 1
);
}
}
}
this.addEventListener = function(eventType, closure)
{
if(owner["on"+eventType] == null)
{
owner["on"+eventType] = [];
}
var events = owner["on"+eventType];
if(events && typeof(events) == "function")
events = [events];
events.push(closure);
}
this.removeEventListener = function(eventType, closure)
{
var events = owner["on"+eventType];
if(events && typeof(events) == "function")
events = [events];
for(var i = 0; i {
if(events[i] == closure)
events.splice(i, 1);
}
}
}
function randomSerials(len)
{
function randomSignal()
{
return Math.random() > 0.5 ? 1 : 0;
}
var ret = [];
for(var i = 0; i {
ret.push(randomSignal());
}
return ret;
}
function Differ(obl)
{
var buffer = new Array(obl);
var time = 0;
EventManager.call(this); //apply EnventManager Component.
this.readBuffer = function()
{
var buf = buffer;
buffer = new Array(obl);
time = 0;
return buf;
}
this.bufferSize = function()
{
return obl;
}
this.input = function(serials)
{
for(var i = 1; i {
var signal = Math.abs(serials[i] - serials[i - 1]);
buffer[time++ % obl] = signal;
if(signal)
this.dispatchEvent("signalchange",
{input:serials, time:time, buffer:buffer.slice(0)});
}
}
}
var inputSerials = randomSerials(20);
alert(inputSerials);
var diff10 = new Differ(20);
diff10.input(inputSerials);
alert(diff10.readBuffer());
var eventHandler1 = function(eventArgs){
alert(eventArgs.time);
}
var eventHandler2 = function(eventArgs){
alert(eventArgs.buffer);
}
diff10.addEventListener("signalchange",eventHandler1);
diff10.addEventListener("signalchange",eventHandler2);
diff10.input(inputSerials);
diff10.removeEventListener("signalchange",eventHandler1);
在上面的例子里,我们建立了一个EventManager类型,为它定义了三个对象方法,dispatchEvent方法和前面那个例子很类似,是用来分派事件的,而另外的addEventListener和removeEventListener则是用来注册和注销事件处理函数。
在Differ类型中,我们通过EventManager.call(this);将EventManager类型的实例运用到Differ原型中(关于这个问题的深层机制,留待以后再进行详细讨论)。然后调用this.dispatchEvent来分派事件。
在为Differ实例的onsignalchange事件代理注册事件时,你会发现它和标准的DOM事件模型非常类似:
diff10.addEventListener("signalchange",eventHandler1);
diff10.addEventListener("signalchange",eventHandler2);
diff10.removeEventListener("signalchange",eventHandler1);
运行过这个例子,你会发现一个有趣的地方,就是diff10.input(inputSerials);触发的事件并没有执行eventHandler1和eventHandler2,而是只执行了eventHandler2,原因是:
diff10.removeEventListener("signalchange",eventHandler1);
先于事件的触发被执行,这是因为事件机制是一种“异步回调”机制,关于同步和异步的问题,我们以后讨论。
4 标准模式:事件分派和接收
在事件处理模式中,事件的分派者负责发出消息,事件的接收者负责处理消息。在前面的例子里,它们是由同一个对象(Differ)完成的。
然而,事实上,事件处理模式中,并不要求消息的发送和接收由同一个对象完成,在某些模式中,它们是不同的对象,其中最常见的一种是“观察者”模式,下面将差分系统的例子改写为观察者模式:
function dispatchEvent(owner, eventType, eventArgs)
{
if(owner && owner["on"+eventType])
setTimeout(function(){owner["on"+eventType](eventArgs)}, 1);
}
function randomSerials(len)
{
function randomSignal()
{
return Math.random() > 0.5 ? 1 : 0;
}
var ret = [];
for(var i = 0; i {
ret.push(randomSignal());
}
return ret;
}
function DifferObserver(differ)
{
this.differ = differ;
differ.setObserver(this);
}
function Differ(obl)
{
var buffer = new Array(obl);
var time = 0;
var observer = null;
this.input = function(serials)
{
for(var i = 1; i {
var signal = Math.abs(serials[i] - serials[i - 1]);
buffer[time++ % obl] = signal;
if(signal)
dispatchEvent(observer, "signalchange", {sender:this, input:serials, time:time, buffer:buffer.slice(0)});
}
}
this.setObserver = function(obs)
{
observer = obs;
observer.readBuffer = function()
{
var buf = buffer;
buffer = new Array(obl);
time = 0;
return buf;
}
observer.bufferSize = function()
{
return obl;
}
}
}
var inputSerials = randomSerials(20);
alert(inputSerials);
var diff10 = new Differ(20);
diff10.input(inputSerials);
var diffObs = new DifferObserver(diff10);
alert(diffObs.readBuffer());
diffObs.onsignalchange = function(eventArgs)
{
if(diff10 == eventArgs.sender)
alert(eventArgs.time);
}
diff10.input(inputSerials);
上面例子中的事件分派者是Differ类型,而事件接收者则是DifferObserver类型,所以事件注册的代理是DifferObserver的属性,在发送的事件参数中,我们增加了一个属性sender,它引用事件的实际发送对象
原文:http://bbs.51js.com/thread-69808-1-1.html by 月影