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左侧多级菜单动态的解决方案
Feb 01 Javascript
JavaScript与DOM组合动态创建表格实例
Dec 23 Javascript
使用JQUERY进行后台页面布局控制DIV实现左右式
Jan 07 Javascript
javascript基本类型详解
Nov 28 Javascript
js中flexible.js实现淘宝弹性布局方案
Jun 23 Javascript
javascript中BOM基础知识总结
Feb 14 Javascript
Angular.JS中的指令引用template与指令当做属性详解
Mar 30 Javascript
JS判断时间段的实现代码
Jun 14 Javascript
jQuery ajax读取本地json文件的实例
Oct 31 jQuery
解决Vue axios post请求,后台获取不到数据的问题方法
Aug 11 Javascript
nuxt引入组件和公共样式的操作
Nov 05 Javascript
利用uni-app生成微信小程序的踩坑记录
Apr 05 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 APC缓存配置、使用详解
2014/03/06 PHP
php递归函数三种实现方法及如何实现数字累加
2015/08/07 PHP
详解WordPress开发中wp_title()函数的用法
2016/01/07 PHP
CodeIgniter框架基本增删改查操作示例
2017/03/23 PHP
用js+xml自动生成表格的东西
2006/12/21 Javascript
如何确保JavaScript的执行顺序 之实战篇
2011/03/03 Javascript
nodejs npm install全局安装和本地安装的区别
2014/06/05 NodeJs
js控制再次点击按钮之间的间隔时间可防止重复提交
2014/08/01 Javascript
javascript中slice(),splice(),split(),substring(),substr()使用方法
2015/03/13 Javascript
javascript遇到html5的一些表单属性
2015/07/05 Javascript
JavaScript数组操作详解
2017/02/04 Javascript
layui实现点击按钮给table添加一行
2018/08/10 Javascript
Vuex 使用及简单实例(计数器)
2018/08/29 Javascript
vue中render函数的使用详解
2018/10/12 Javascript
详解Vue 如何监听Array的变化
2019/06/06 Javascript
原生JS实现留言板
2020/03/26 Javascript
js HTML DOM EventListener功能与用法实例分析
2020/04/27 Javascript
node.js爬虫框架node-crawler初体验
2020/10/29 Javascript
jQuery实现动态操作table行
2020/11/23 jQuery
使用Python内置的模块与函数进行不同进制的数的转换
2016/03/12 Python
python 实现上传图片并预览的3种方法(推荐)
2017/07/14 Python
pandas Dataframe行列读取的实例
2018/06/08 Python
Python在图片中插入大量文字并且自动换行
2019/01/02 Python
使用OpenCV实现仿射变换—旋转功能
2019/08/29 Python
使用python实现kNN分类算法
2019/10/16 Python
详解python中*号的用法
2019/10/21 Python
python矩阵运算,转置,逆运算,共轭矩阵实例
2020/05/11 Python
迟到检讨书300字
2014/02/14 职场文书
消防安全承诺书
2014/05/22 职场文书
电大奖学金获奖感言
2014/08/14 职场文书
反四风个人对照检查材料思想汇报
2014/09/25 职场文书
通知书大全
2015/04/27 职场文书
学校教师师德师风承诺书
2015/04/28 职场文书
2015年全国助残日活动方案
2015/05/04 职场文书
阿甘正传观后感
2015/06/01 职场文书
2016年教师新年寄语
2015/08/18 职场文书