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 键盘记录实现(兼容FireFox和IE)
Feb 07 Javascript
JavaScript性能陷阱小结(附实例说明)
Dec 28 Javascript
JavaScript使用yield模拟多线程的方法
Mar 19 Javascript
ztree获取选中节点时不能进入可视区域出现BUG如何解决
Dec 03 Javascript
JavaScript数据推送Comet技术详解
Apr 07 Javascript
JQuery解析XML数据的几个简单实例
May 18 Javascript
深入理解jquery自定义动画animate()
May 24 Javascript
jQuery监听文件上传实现进度条效果的方法
Oct 16 Javascript
利用imgareaselect辅助后台实现图片上传裁剪
Mar 02 Javascript
JavaSctit 利用FileReader和滤镜上传图片预览功能
Sep 05 Javascript
Vue 动态设置路由参数的案例分析
Apr 24 Javascript
vue如何使用async、await实现同步请求
Dec 09 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
PHP中获取变量的变量名的一段代码的bug分析
2011/07/07 PHP
浅析php与数据库代码开发规范
2013/08/08 PHP
CentOS6.5 编译安装lnmp环境
2014/12/21 PHP
php获得客户端浏览器名称及版本的方法(基于ECShop函数)
2015/12/23 PHP
PHP文件操作实例总结【文件上传、下载、分页】
2018/12/08 PHP
JavaScript 匿名函数(anonymous function)与闭包(closure)
2011/10/04 Javascript
PHP开发者必须掌握的6个关键字
2014/04/14 Javascript
jquery网页回到顶部效果(图标渐隐,自写)
2014/06/16 Javascript
深入理解JavaScript系列(37):设计模式之享元模式详解
2015/03/04 Javascript
jquery 判断是否支持Placeholder属性的方法
2017/02/07 Javascript
Bootstrap实现下拉菜单多级联动
2017/11/23 Javascript
Vue 进入/离开动画效果
2017/12/26 Javascript
JS+canvas画布实现炫酷的旋转星空效果示例
2019/02/13 Javascript
mpvue微信小程序多列选择器用法之省份城市选择的实现
2019/03/07 Javascript
更优雅的微信小程序骨架屏实现详解
2019/08/07 Javascript
微信小程序实现注册登录功能(表单校验、错误提示)
2019/12/10 Javascript
JavaScript Tab菜单实现过程解析
2020/05/13 Javascript
一篇文章带你从零快速上手Rollup
2020/09/07 Javascript
JavaScript日期库date-fn.js使用方法解析
2020/09/09 Javascript
基于原生JS封装的Modal对话框插件的示例代码
2020/09/09 Javascript
返回上一个url并刷新界面的js代码
2020/09/12 Javascript
python模拟新浪微博登陆功能(新浪微博爬虫)
2013/12/24 Python
python实现提取百度搜索结果的方法
2015/05/19 Python
Python探索之pLSA实现代码
2017/10/25 Python
python 解决动态的定义变量名,并给其赋值的方法(大数据处理)
2018/11/10 Python
Wallis官网:英国女装零售商
2020/01/21 全球购物
留学推荐信中文范文三篇
2014/01/25 职场文书
物理系毕业生自荐书范文
2014/02/22 职场文书
高中生职业规划范文
2014/03/09 职场文书
2014年社区重阳节活动策划方案
2014/09/16 职场文书
市场营销计划书范文
2015/01/16 职场文书
公司考勤管理制度
2015/08/04 职场文书
队列队形口号
2015/12/25 职场文书
《梅花魂》教学反思
2016/02/18 职场文书
Golang Gob编码(gob包的使用详解)
2021/05/07 Golang
Python爬虫实战之爬取携程评论
2021/06/02 Python