65.9K
CodeProject 正在变化。 阅读更多。
Home

JS 事件框架用于 OO 架构

starIconstarIconstarIcon
emptyStarIcon
starIcon
emptyStarIcon

3.67/5 (2投票s)

2010 年 5 月 18 日

CPOL

2分钟阅读

viewsIcon

10472

一个适用于所有JavaScript需求的、可重用的事件框架。

引言

本文将介绍我开发的事件框架。它允许我以更传统的面向对象方式进行JavaScript开发。

背景

在HTML 4中作为客户端编辑器时,我迫切需要一种创建事件的简单方法,该方法与其他语言类似,即可以向事件注册多个委托实例,并在事件触发时调用所有实例。

JavaScript中的方法调用

许多语言都具有反射库,以便充分利用面向对象语言的多态性。JavaScript没有反射库,但你可以控制方法运行的上下文来调用方法。

该过程使用函数的“call”方法进行。以下示例显示了“call”方法的简单实现

var fTest = function() { alert('test'); }
fTest.call();

这将调用fTest方法并显示警报框。

你还可以通过“call”在调用方法时提供上下文。这里的上下文指的是方法体内的对象引用作为“this”。考虑以下示例

var fTest = function(text) { alert(this.Name + ': ' + text); }
var context = { Name: 'dave' };
fTest.call(context, 'forename');

生成的警报将显示“dave: forename”。由于context JSON对象作为方法调用的“this”引用,因此this.Name返回“dave”。后续参数传递到“call”方法并作为常规参数传递。

代码

代码的第一部分通过扩展Array对象,添加AddRemoveAtIndexOf方法来改进JavaScript中的Array对象。

在事件框架中,数组对象用于存储已注册操作的列表。

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; 
}

下一部分定义了uiEvent对象。这是实例化以创建新事件的对象。本文档稍后将提供一个实现示例。

 var uiEventRegistration = function() {
  this.ID = null;
  this.Method = null;
  this.Caller = null;
}
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])
    }
  }
}

现在我们有一个名为uiEvent的类,可用于在JavaScript中创建传统事件。以下示例是框架的基本实现。

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');

在上面的示例中,将出现两个警报。第一个会说“Dave is sad”。第二个会说“sad is not a good thing for Chris”。

不公平的优先级

在IE中,UI事件(例如onmousemoveonkeypress)比从setTimeoutsetInterval执行的调用具有更高的优先级。这在开发动画框架时(该框架将在后续文章中进一步讨论)造成了一个问题。

当我构建一个拖放编辑器时,大量代码链接到onmousemove事件。这并不总是会在下一个onmousemove事件在JavaScript引擎中注册之前完成。这导致setInterval调用被阻止,直到鼠标保持静止。

为了反转优先级并首先强制基于计时器的事件触发,我已在事件框架中包含以下类

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() { return this.NextTime < new Date(); };
  this.TimerID = uiTimerCount;
  setInterval('uiTimerExecute(' + this.TimerID + ');', interval);
}

uiTimer类将跟踪计划的间隔是否已过期,并以高于正常优先级的优先级引发事件,因为事件框架会检查它是否已过期,而不是等待setInterval评估运行。

以下是uiTimer的实现示例

var secondTicks = 0;

function secondIncrement() {
  secondTicks++;
  window.status = secondTicks;
}

var secondTimer = new uiTimer(1000);

secondTimer.OnTick.Register(secondIncrement);
© . All rights reserved.