这一次我要解释最优秀的事件处理函数绑定方式:确保当事件发生在某个html元素上时,能有相应的脚本与之对应
在早期运行javascript的浏览器中,处理函数绑定只能通过行内模型。但自从DHTML彻底改变生成网页的方式后,事件绑定模型也得到拓展并且更加灵活。所以浏览器厂商引进了新的事件模型。Netscape是从第三代开始的,而IE在第四代中也同样引进。
因为Netscape3已经支持新的绑定模型,所以至少在浏览器大战之前,从某种意义上来说它已经是一种执行的标准,。因此,微软为了使它的浏览器具有兼容性(因为大多数网页都遵循了Netscape的事件模型),最后一次不得不也采用了这种标准。
所以两家浏览器,事实上是所有的浏览器,都认可这段代码:
element.onclick = doSomething;
作为一种绑定事件函数的正确方式。无论用户什么时候在什么浏览器上点击这个HTML元素,函数,doSomething()都会被执行。因为这种写法是通用,更因为它是跨浏览器绑定事件函数的唯一方式。但有一点非常重要,就是你也要非常明白这种方式的局限性和应该出现的场合。
因为当这种模型被引进时没有任何的官方标准可以遵循,所以我把它称为传统事件绑定模型。与此同时W3C已经标准化了事件绑定,并且微软也发明了更先进的模型(参见下一篇),但是传统的模型仍然能良好的工作。
更先进的事件绑定
在Netscape3和Explorer4之后,javascript对每一种可以发生在元素上的事件,都作为自己的一种属性。因此HTML元素有onclick,onmouseover,onkeypress属性等。但究竟哪一种html元素有哪一种属性,哪一种html元素支持事件——依据浏览器而定。
就这些属性而言,并不是什么很新鲜的东西。在早期的javascript浏览器中已经存在了:
<a href="somewhere.html" onclick="doSomething()">
这个A标签有一个onclick属性,也就意味着javascript变成了一种元素的属性。在早期的浏览器中,处理函数用这种写入标签的方式。所以如果你想在页面上的每一个超链接都有同样的处理函数,工作量是非常巨大的。
随着传统绑定模型的出现,onclick,onmouseover等所有html元素的事件属性都可以通过javascript访问。在你通过DOM访问html元素后,现在你可以在最小程度的修改html代码的基础上,新增,修改或者移除处理函数。你可以把你的函数写入html的属性中:
element.onclick = doSomething;
在我们的例子中,函数doSomething()绑定到onclick属性上,并且无论用户点击这个元素,函数都会被执行。注意事件名称必须小写。
要移除处理函数,只要简单的把onclick方法置空
element.onclick = null;
处理函数也是一个普通的javascript函数,就算事件没有发生它也能被执行,如果你这么作:
element.onclick()
doSomething()将被执行,虽然没有事件真的发生。但是这种执行处理函数的方式使用的并不非常多
微软也为自己的IE5.5和之后的IE新增了fireEvent()方法,目的是为了实现上面的效果。
语法应该这么写
element.fireEvent('onclick')
没有括号!
请注意在绑定处理函数过程中不能使用括号()。分配给onclick给它的是整个函数。如果你这么做:
element.onclick = doSomething();
函数将会被执行,却是它的结果被绑定在onclick上。这并不是我们想要的,我们希望当事件发生时函数被执行。再者说,通常函数是期望有相应的事件发生,如果在没有上下文的情况下就执行它,会引起错误。
this关键字
在javascript中this关键字总是对一个函数“拥有者”的引用。在处理函数的例子中,如果this是代指正在处理事件的html元素,这将会非常有用。你可以很轻松的访问它。
不幸的是this关键字虽然非常强大,但是如果你不是非常清楚它的工作原理的话,使用起来也很困难。我在另一篇文章中有谈论。这里我只给出一个简短的结论。
在传统绑定模式中的this关键字,同行内绑定模式(上一篇)中的有所不同。在这里this关键字存在于函数中,而不是Html某个属性。具体的不同之处会在另一个页面单独指出
element.onclick = doSomething;
another_element.onclick = doSomething;
function doSomething() {
this.style.backgroundColor = '#cc0000';
}
如果你给任何一个Html元素的click事件绑定一个doSomething()函数,则用户在任何时候点击它时。它的背景都会变为红色。
匿名函数
假设你想在改变所有DIV的背景颜色,在onmouseover是改变在onmouseout时恢复,恰当的this应该这么使用:
var x = document.getElementsByTagName('DIV');
for (var i=0;i<x.length;i++) {
x[i].onmouseover = over;
x[i].onmouseout = out;
}
function over() {
this.style.backgroundColor='#cc0000'
}
function out() {
this.style.backgroundColor='#ffffff'
}
代码当然能正常工作,但是over()和out()函数这么简单,把他们绑定为匿名函数更为明智:
...
for (var i=0;i<x.length;i++) {
x[i].onmouseover = function () {this.style.backgroundColor='#cc0000'}
x[i].onmouseout = function () {this.style.backgroundColor='#ffffff'}
}
onmouseover和onmouseoout期望能匹配对应的处理函数,相比复制over()和out()函数而言,我们在事件绑定脚本中立即定义了处理函数。因为他们没有函数名,所以称之为匿名函数。
这两种绑定事件处理函数的方法是完全相同的的,唯一的不同之处是第二个用更少的代码。当我需要绑定一个简单的事件处理函数时,我更乐意使用匿名函数。
问题
传统模式的一个明显缺陷的就是onclick只能容下一个函数,那么当你想为一个事件绑定多个处理函数时这就变成了一个大问题。
举个例子,假设你在写一个功能模块,实现一个元素的拖拽。模块借助于一个onclick处理函数上,当点击这个元素时开始执行拖拽。你同时也在实现另一个模块,能悄悄追踪用户的点击情况,并且在发生onunload事件时,把信息发送给服务器,你就可以发现用户到底是如何使用你的页面的。这个模块,同样借助于onclick处理函数。
所以你可以这么做:
element.onclick = startDragDrop;
element.onclick = spyOnUser;
但是问题发生了。第二个绑定onclick上的函数会覆盖第一个函数,所以当用户点击某个元素时,只有第二个spyOnUser被执行。解决方案当然是绑定一个能同时执行两个函数的函数
element.onclick = function () {startDragDrop(); spyOnUser()}
更灵活的绑定方式
但是假设你不需要在每一个页面都需要这两个功能。现在如果你真的这么做:
element.onclick = function () {startDragDrop(); spyOnUser()}
你也许会得到错误的信息,因为这两个函数的其中之一可能还没有定义。当我们想要绑定spyOnUser()时,startDrapDrop()可能还没有绑定,所以我们这么做:
var old = (element.onclick) ? element.onclick : function () {};
element.onclick = function () {old(); spyOnUser()};
首先你要定义一个名为old的变量,如果这个元素有一个onclick事件处理函数,把这个函数存储于old中,如果它没有,把一个空函数放进old中。再给div元素绑定一个新的处理函数,它首先执行的是old,再是spyOnUser()
现在新的事件处理函数添加给元素了,并且之前的处理(如果有的话)还能得以保存
还有最后一个问题:如果你想移除其中一个处理函数,而不是全部的,应该怎么办?现在我不知道应该如何是好了。你又不得不以另一种方式再次编辑element.onclick事件,但是我并不想真的去研究这个问题
其他的模型
现在我们已经见识到传统的事件绑定模型使用起来非常简单,但是也存在一些棘手的问题,比如说当你想给同一个事件添加不止一个处理函数时。所以W3C事件绑定模型很好的解决了这个问题
下篇继续谈《先进的绑定模型》