javascript事件模型代码


Posted in Javascript onJuly 01, 2007

本节稍稍深入地讨论关于事件处理的话题,如果你对模式、闭包和面向对象等概念还不太理解,不妨暂且等阅读完相关内容之后再回过头来阅读它,相信你会有很大收获。

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是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 月影

Javascript 相关文章推荐
js 获取屏幕各种宽高的方法(浏览器兼容)
May 15 Javascript
javascript中apply和call方法的作用及区别说明
Feb 14 Javascript
JSP中使用JavaScript动态插入删除输入框实现代码
Jun 13 Javascript
jQuery延迟加载图片插件Lazy Load使用指南
Mar 25 Javascript
浅析Javascript的自动分号插入(ASI)机制
Sep 29 Javascript
TypeScript学习之强制类型的转换
Dec 27 Javascript
jQuery中$原理实例分析
Aug 13 jQuery
浅谈ng-zorro使用心得
Dec 03 Javascript
JavaScript实现预览本地上传图片功能完整示例
Mar 08 Javascript
vue中实现高德定位功能
Dec 03 Javascript
详解vue中在循环中使用@mouseenter 和 @mouseleave事件闪烁问题解决方法
Apr 07 Javascript
vue如何在用户要关闭当前网页时弹出提示的实现
May 31 Javascript
如何快速的呈现我们的网页的技巧整理
Jul 01 #Javascript
IE autocomplete internet explorer's autocomplete
Jun 30 #Javascript
用javascript实现的激活输入框后隐藏初始内容
Jun 29 #Javascript
javascritp实现input输入框相关限制用法
Jun 29 #Javascript
优化网页之快速的呈现我们的网页
Jun 29 #Javascript
javascript实现动态CSS换肤技术的脚本
Jun 29 #Javascript
javascript之锁定表格栏位
Jun 29 #Javascript
You might like
有关JSON以及JSON在PHP中的应用
2010/04/09 PHP
检查php文件中是否含有bom的函数
2012/05/31 PHP
php统计文章排行示例
2014/03/04 PHP
php安装swoole扩展的方法
2015/03/19 PHP
Linux下快速搭建php开发环境
2017/03/13 PHP
windows下的WAMP环境搭建图文教程(推荐)
2017/07/27 PHP
JQuery 操作Javascript对象和数组的工具函数小结
2010/01/22 Javascript
jquery中输入验证中一个不错的效果
2010/08/21 Javascript
DWZ刷新dialog解决方法
2013/03/03 Javascript
JS关闭窗口或JS关闭页面的几种代码分享
2013/10/25 Javascript
js实现鼠标经过时图片滚动停止的方法
2015/02/16 Javascript
Nodejs进阶:如何将图片转成datauri嵌入到网页中去实例
2016/11/21 NodeJs
微信小程序中实现一对多发消息详解及实例代码
2017/02/14 Javascript
Vue精简版风格概述
2018/01/30 Javascript
JavaScript实现微信号随机切换代码
2018/03/09 Javascript
详解JavaScript中的强制类型转换
2019/04/15 Javascript
Vue 中使用 typescript的方法详解
2020/02/17 Javascript
JQuery获得内容和属性方法解析
2020/05/30 jQuery
基于Vue sessionStorage实现保留搜索框搜索内容
2020/06/01 Javascript
Python完全新手教程
2007/02/08 Python
Python中super函数的用法
2017/11/17 Python
pandas dataframe添加表格框线输出的方法
2019/02/08 Python
Django网络框架之HelloDjango项目创建教程
2019/06/06 Python
keras打印loss对权重的导数方式
2020/06/10 Python
python如何输出反斜杠
2020/06/18 Python
python实现AHP算法的方法实例(层次分析法)
2020/09/09 Python
python pip如何手动安装二进制包
2020/09/30 Python
html5中的input新属性range使用记录
2014/09/05 HTML / CSS
中餐厅主管的职责范文
2014/02/04 职场文书
园林设计专业毕业生求职信
2014/03/23 职场文书
财务负责人任命书
2014/06/06 职场文书
社保委托书怎么写
2014/08/02 职场文书
催款函怎么写
2015/06/24 职场文书
找规律教学反思
2016/02/23 职场文书
关于CentOS 8 搭建MongoDB4.4分片集群的问题
2021/10/24 MongoDB
SpringCloud项目如何解决log4j2漏洞
2022/04/10 Java/Android