使用JavaScript的自定义事件

分享于 

8分钟阅读

Web开发

  繁體 雙語

介绍

本文将介绍事件框架。 将有一个解释框架和一个实现示例。

Background

在生成拖放编辑器时,我需要一个事件框架,它允许我使用事件( 请原谅. NET 术语) 对多个委托进行 register。 以下是该需求的结果。

这里框架还包括实现计时器的代码。 我将解释为什么在"兴趣点"部分需要这样做。

代码

下面是事件框架的代码。

使用Prototype制作轻松的生活

大多数框架中的集合包括在 Collection 中添加和移除项的方法。 默认情况下,JavaScript并不存在,但是可以使用Prototype来添加它们。 这个框架Prototype的第一部分在 Array 之上,用 AddRemoveAtIndexOf 函数扩展它。

Array.prototype.Add = function(obj) { this[this.length] = obj; }
Array.prototype.RemoveAt = function(index) {
 var result = new Array();
 var offset = 0;
 for (var x = 0; x <this.length; x++) {
 if (x!= index)
 result[result.length] = this[x];
 }
 return result;
}
Array.prototype.IndexOf = function(value) { for (var x = 0; x <
 this.length; x++) if (this[x] == value) return x; return -1; }var uiEventRegistration = function() {
 this.ID = null;
 this.Method = null;
 this.Caller = null;
}

下面的代码创建了一个使用计时器的框架。 有一个定时器 array 用来保存所有已经创建的计时器。 一个名为" uiTimerExecute"的函数用来检查计时器过期状态。 还有一个 uiTimer 类,它在构造函数中对一个区间( 以毫秒为单位) 进行 recieves。

var uiTimers = new Array();var uiTimerCount = 0;function uiTimerExecute(timerID) {
 for(var x = 0; x <uiTimers.length; x++) {
 if (timerID == null || uiTimers[x].TimerID == timerID) {
 if (uiTimers[x].Expired()) {
 uiTimers[x].OnTick.Fire();
 var d = new Date();
 uiTimers[x].NextTime = new Date(d.getYear(),d.getMonth(),d.getDate(), 
 d.getHours(),d.getMinutes(),d.getSeconds(), 
 d.getMilliseconds() + (uiTimers[x].Interval));
 }
 }
 }
}var uiTimer = function(interval) {
 uiTimers.Add(this);
 this.Interval = interval;
 this.StartDate = new Date();
 this.NextTime = new Date(this.StartDate.getYear(),this.StartDate.getMonth(),
 this.StartDate.getDate(),this.StartDate.getHours(), 
 this.StartDate.getMinutes(),
 this.StartDate.getSeconds(),
 this.StartDate.getMilliseconds() + (interval));
 this.OnTick = new uiEvent();
 this.OnTick.TimerEvent = true;
 this.Expired = function() { returnthis.NextTime <new Date(); };
 this.TimerID = uiTimerCount;
 setInterval('uiTimerExecute(' + this.TimerID + ');', interval);
}

最后一节是事件框架的主要部分。 有一个叫 uiEvent的类。 这有一个方法" Register"。 这将传递一个委托和一个上下文对象。 上下文对象是指的对象,称为" 这个 "方法调用委托时"。 Register 返回注册标识。 这可以与" Deregister"方法一起使用,以从事件堆栈中移除委托。 最后一个方法是" Fire"。 调用这里方法时,传入的所有参数随后作为参数发送到注册代理。

var uiEvent = function() {
 this.TimerEvent = false;
 this.__registered = new Array();
 this.__currentID = 0;
 this.Register = function(func, caller) {
 var reg = new uiEventRegistration();
 reg.ID = this.__currentID;
 reg.Method = func;
 reg.Caller = caller;
 this.__registered.Add(reg);
 var returnID = this.__currentID;
 this.__currentID++;
 return returnID;
 }
 this.Deregister = function(RegistrationID) {
 var index = -1;
 for (var x = 0; x <this.__registered.length; x++)
 if (this.__registered[x].ID == RegistrationID) {
 index = x;
 break;
 }
 if (index!= -1) {
 this.__registered = this.__registered.RemoveAt(index);
 }
 }
 this.Fire = function() {
 if (!this.TimerEvent)
 uiTimerExecute();
 var a = arguments;
 for (var x = 0; x <this.__registered.length; x++) {
 this.__registered[x].Method.call(this.__registered[x].Caller,
 a[0] == null? null : a[0],
 a[1] == null? null : a[1],
 a[2] == null? null : a[2],
 a[3] == null? null : a[3],
 a[4] == null? null : a[4],
 a[5] == null? null : a[5],
 a[6] == null? null : a[6],
 a[7] == null? null : a[7],
 a[8] == null? null : a[8],
 a[9] == null? null : a[9])
 }
 }
}

对于 Fire 事件,有两种可能的方法。 函数作为参数传递时,可以在函数上执行" call"方法。 这与通过反射调用具有相同的效果。 第二种方法是使用 eval 调用方法。 eval 比使用" call"方法要慢得多。 在使用" call"方法时,有一个限制。 需要显式地将 call的参数包括在调用中。 这意味着对事件可以处理的参数数量有固定的限制。 好消息是,框架可以扩展为处理所需的数量。 调用 上面 包括以下参数: a[0] == null? null : a[0]

通过继续以前的索引位置 9,你可以根据实现的需要扩展参数计数限制。

使用代码

下面是框架的最基本实现之一。 请尝试以下代码:

var eventTest = new uiEvent();var context1 = { Name: 'Dave' };var context2 = { Name: 'Chris' };function delegate1(mood) {
 alert(this.Name + ' is ' + mood);
}function delegate2(mood) {
 alert(mood + ' is not a good thing for ' + this.Name);
}
eventTest.Register(delegate1, context1);
eventTest.Register(delegate2, context2);
eventTest.Fire('sad');

将显示以下消息框:

  • "is"
  • "for"不是Chris的好事"

Points of Interest

在DOM中有基本事件,如 onmousemoveonkeypress。 你假定使用单一线程,所有代码堆栈都将被分配相同的优先级并按顺序处理。 这不是一个例子,比如 如果你有一个鼠标移动事件,那是一个非常重要的过程。 如果在前一次完成之前,鼠标移动事件发生在( 线程上没有空闲时间) 中,鼠标移动事件将始终优先于使用 setIntervalsetTimeout 计划的执行。 在实现动画框架时,这将成为一个问题,将在后面的文档中描述。

因此,在这个框架中,我创建了一个计时器事件。 当非计时器事件触发时,它检查所有已经注册计时器事件,看看它们是否已经过期,如果是,则首先执行。 下面是计时器事件的示例实现。

本示例实现了第二个计时器,它每秒增加一次计数,并更新窗口状态。

var secondTicks = 0;function secondIncrement() {
 secondTicks++;
 window.status = secondTicks;
}var secondTimer = new uiTimer(1000);
secondTimer.OnTick.Register(secondIncrement);

JAVA  Javascript  EVE  event  事件