Web浏览器中的JavaScript实现允许我们定义响应用户事件(通常是鼠标或者键盘事件)所执行的代码。在支持Ajax的现代浏览器中,这些事件处理函数可以被设置到大多数可视元素之上。我们可以使用事件处理函数将可视用户界面(即视图)与业务对象模型相连接。
传统的事件模型在JavaScript诞生的早期就存在了,它是相当简单和直接的。DOM元素有几个预先定义的属性,可以赋值为回调函数。例如,为了附加一个在鼠标点击元素myDomElement时被调用的函数,我们可以这样写:
myDomElement.onclick=showAnimatedMonkey myDomElement是可以通过程序处理的任何DOM元素。showAnimatedMonkey是一个函数,定义为: function showAnimatedMonkey(){
//some skillfully executed code to display
//an engaging cartoon character here
}
这只是一个普通的JavaScript函数。注意,当我们分配事件处理函数的时候,传递的是一个Function对象,而不是对那个对象的调用,因此它在函数名后面不包含圆括号。下面是一个常见的错误:
myDomElement.onclick=showAnimatedMonkey();这对于不习惯将函数看作正常对象的程序员来说更加自然一些,但是它不会按照我们所设想的方式工作。函数将在进行赋值时被调用,而不是当点击DOM元素时才被调用。onclick属性将被设置为函数返回的任何值。除非你编写一些非常巧妙的包含函数(involving function),可以返回对其他函数的引用,否则产生的效果可能不是你所希望的。正确的方法是:
myDomElement.onclick=showAnimatedMonkey;这里向DOM元素传递了一个回调函数的引用,告诉它这是当点击节点时需要调用的函数。DOM元素有很多此类的属性,事件处理函数可以附加在这些属性上。用于GUI的常用事件处理回调函数列举在表4-1中。类似的属性也可以在Web浏览器JavaScript的其他地方见到。我们已经遇到的XMLHttpRequest.onreadystate和window.onload也是由程序员赋值的事件处理函数。
表4-1 DOM中的常用GUI事件处理函数属性
属 性 |
描 述 |
onmouseover |
当鼠标首次进入元素区域的时候触发 |
onmouseout |
当鼠标离开元素区域的时候触发 |
onmousemove |
任何时候,当鼠标在元素区域中移动的时候触发(即频繁地触发) |
onclick |
当鼠标在元素区域内被点击的时候触发 |
onkeypress |
|
onfocus |
可视元素获得了输入焦点 |
onblur |
可视元素丧失了输入焦点 |
事件处理函数有一个不寻常的特征需要在这里提一下,当编写面向对象的JavaScript时,它最容易让人出错,这也是在开发Ajax客户端时要严重依赖的特征。
我们已经得到了一个DOM元素的句柄,分配了一个回调函数给onclick属性。当DOM元素收到鼠标点击事件时,回调即被调用。然而,函数上下文(即变量this所确定的值——参见附录B,可获得关于JavaScript Function对象的完整讨论)赋值为收到事件的DOM节点。根据函数最初是在什么地方声明以及如何声明的,情况会有所不同,这可能会把人搞糊涂。
让我们通过一个例子来研究这个问题。我们定义了一个表示按钮对象的类,它有一个到DOM节点的引用、一个回调处理函数,以及当点击按钮时显示出的一个值。当鼠标点击事件发生时,按钮的任何实例都将以同样的方式响应,因此我们定义回调处理函数作为按钮类的一个方法。这些说明对于初学者已经足够了,下面让我们看看代码。这里是按钮类的构造函数。
function Button(value,domEl){
this.domEl=domEl;
this.value=value;
this.domEl.onclick=this.clickHandler;
}
继续定义一个事件处理函数作为Button类的一部分。
Button.prototype.clickHandler=function(){
alert(this.value);
}
这段代码看起来很直观,但是它并没有做我们希望它做的事情。警告框通常会返回消息unde- fined,而不是传递到构造函数的value属性。为什么呢?当点击DOM元素时,函数click- Handler由浏览器调用,它设置函数上下文到DOM元素,而不是Button的JavaScript对象。于是,this.value指向DOM元素的value属性,而不是Button对象的value属性。你永远也不可能通过查看事件处理函数的声明来发现这个情况,是不是?
我们可以通过向DOM元素传递Button对象的引用来解决这个问题,也就是,按下面的方法修改构造函数:
function Button(value,domEl){
this.domEl=domEl;
this.value=value;
this.domEl.buttonObj=this;
this.domEl.onclick=this.clickHandler;
}
DOM元素仍然没有value属性,但是它有一个到Button对象的引用,可以从那里得到value。通过对事件处理函数做如下修改,我们的工作就完成了:
Button.prototype.clickHandler=function(){
var buttonObj=this.buttonObj;
var value=(buttonObj && buttonObj.value) ?
buttonObj.value : "unknown value";
alert(value);
}
DOM节点引用Button对象,Button对象引用它的value属性,这样事件处理函数就做了我们希望它做的事情。我们可以直接给DOM节点附加value,不过附加一个指向整个后端对象的引用可以使这种模式容易地用于任意复杂的对象,顺便说一句,我们在这里已经实现了一个小的MVC模式,其中DOM元素作为后端对象模型的前端视图。
以上讨论的就是传统的事件模型。这种事件模型主要的缺点是每个元素只允许有一个事件处理函数。在第3章介绍的Observer模式中,我们注意到一个可观察的元素在给定时间可以有任意多个观察者附加在上面。当为Web页面编写简单的脚本时,这可能不会成为一个严重的缺点,但是当迈向更加复杂的Ajax客户端的时候,我们开始感觉到了更多的限制。我们将在4.3.3节中更为密切地考察这个问题,现在来看看新近出现的事件模型