JavaScript事件处理模式详解:从基础分派到观察者模式

作者:交互手记

时间:2026-10-21

浏览:500

本文深入解析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 月影


实用工具推荐

电子包浆

绿化、压缩、噪点和虚拟水印,自由调节做旧程度。

使用 ›
图片压缩

选择画质和输出格式,预览压缩前后的大小。

使用 ›
相关文章