JavaScript事件处理模式详解:从基础分派到观察者模式
本文深入解析JavaScript中的事件处理模式,涵盖从基础的事件分派函数dispatchEvent到支持多监听器的EventManager实现。通过差分系统示例,详细演示了事件代理的注册与注销机制,并探讨了同步与异步回调的差异。最后,文章将事件模型扩展至观察者模式,展示了发送者与接收者分离的实现方式及sender属性的应用。
JavaScript事件处理模式详解:从基础分派到观察者模式
本节将深入探讨事件处理的相关话题。若读者对设计模式、闭包或面向对象编程等概念尚不熟悉,建议先阅读相关基础内容,再回来看此部分,相信会有更深刻的理解与收获。
事件处理模式基础
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 < len; 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 < serials.length; 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指定了事件的“所有者”,即事件由谁接收和处理。在之前的例子中,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 < events.length; 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 < events.length; 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 < len; 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 < serials.length; 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 < len; 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 < serials.length; 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 月影
JS怎么实现页面强制刷新
2026-11-05 14:53:38
点击JS链接直接运行不下载?前端实现.js文件强制下载的三种方法
2026-11-05 12:12:23
怎么封装一个通用的JS创建元素辅助函数
2026-11-05 11:41:41
Swiper.js 怎么监听滑动结束并获取当前索引
2026-11-05 10:57:15
HTML、CSS和JS的关系:Web前端三件套怎么协作
2026-11-04 15:41:40
