事件流
事件流描述的是从页面中接收事件的顺序。IE的事件流是事件冒泡流,而Netscape Communicator的事件流是事件捕获流。
事件冒泡
IE的事件流叫做冒泡流,即事件开始时由最具体的元素接收,然后逐级向上传播到比较不具体的结点。例:
<!DOCTYPE html>
<html>
<head><title></title> </head>
<body>
<div id="button">Click</div>
</body>
</html>
当点击页面中的
(1)div
(2)body
(3)html
(4)document
click事件首先在div元素生发生,而这个元素就是单击的元素。然后,click事件沿DOM树向上传播,在每一级节点上都会发生,直至传播到document对象。
下图展示了事件冒泡的过程。
所有现代浏览器都支持事件冒泡,但在实际上还是有一些差别。IE5.5及早版本中的事件冒泡会跳过html元素(从body直接跳到docuemnt)。IE9、Firefox、Chrome和Safair则将事件一直冒泡到window对象。
事件捕获
Netscape Communicator团队提出的另一种事件流叫做事件捕获。事件捕获的思想是不太具体的节点应该及早收到事件,而最具体的节点应该最后接到事件。事件捕获的用意在于在事件到达预定目标之前捕获它。
单击div元素会以下列顺序触发click事件
(1)document
(2)html
(3)body
(4)div
在事件捕获的过程中,document对象首先接收到click事件,然后事件沿DOM树依次向下,一直传播到事件的实际目标,即div元素。如图,展示了事件捕获的过程。
由于老版本的浏览器不支持,因此很少使用事件捕获。
DOM事件流
“DOM2级事件”规定的事件流包括三个阶段:事件捕获阶段、处于目标阶段和事件冒泡阶段。首先发生的是事件捕获,为截获事件提供了机会。然后是实际的目标接收到事件。最后一个阶段是冒泡阶段,可以在这个阶段对事件作出响应。
单击div元素会按照下图所示顺序出发事件。
在DOM事件流中,实际的目标在捕获阶段不会收到事件。这意味着在捕获阶段,事件从document到再到后就停止了。下一个阶段是”处于目标”阶段,于是事件在
多数支持DOM事件流的浏览器都实现了一种特殊的行为:即使”DOM2级事件”规范明确要求捕获阶段不会涉及事件目标,但IE9、Firefox、Chrome、Opera9.5和Safair及更高版本都会自捕获阶段触发事件对象上的事件。结果,就是有两个机会在目标对象上面操作事件。
IE9、Opera、Firefox、Chrome和Safair都支持DOM事件流,IE8及更早版本不支持DOM事件流。
事件处理程序
事件就是用户或浏览器自身执行的某种动作。诸如click、load和mouseover,都是事件的名字。而响应某个事件的函数就叫做事件处理程序(或事件侦听器)。事件处理程序的名字以”on”开头,因此click事件的事件处理程序就是onclick,load的事件处理程序就是onload。为事件指定处理程序的方式有好几种。
(1)HTML事件处理程序
<input type="button" value="Click Me" onclick="alert('click')" />
在HTML中定义的事件处理程序可以包含要执行的具体动作,也可以调用在页面其他地方定脚本。例如:
<script>
function showMessage(){
alert("click");
}
</script>
<input type="button" value="Click Me" onclick="showMessage()" />
主要缺点:
HTML与JavaScript代码紧密耦合。如果要更换事件处理程序,就要改动两个地方:HTML代码和JavaScript代码。这也正是许多开发人员摒弃HTML事件处理程序,转而使用JavaScript指定事件处理程序的原因所在。
(2)DOM0级事件处理程序
使用DOM0级方法指定的事件处理程序被认为是元素的方法。因此,这时候的事件处理程序是在元素的作用域中运行;换句话说,程序中的this引用当前元素。例如:
var btn = document.getElementById('button');
btn.onclick = function(){
alert(this.id); //"button"
}
不仅仅是id,实际上可以在事件处理程序中通过this访问元素的任何属性和方法。以这种方式添加的事件处理程序会在事件流的冒泡阶段被处理。
可以删除通过DOM0级方法指定的事件处理程序,只需向下面这样将事件处理程序属性的值设置为null。
btn.onclick = null; //删除事件处理程序
将事件处理程序设置为null之后,再单击按钮将不会有任何动作。
DOM2级事件处理程序
“DOM2级事件”定义了两个方法,用于处理指定和删除事件处理程序的操作:addEventListener()和removeEventListener()。所有的DOM节点中都包含这两个方法,并且他们都接受三个参数:要处理的事件名、作为事件处理程序的函数和一个布尔值。其中,布尔值如果是true,表示在捕获阶段调用事件处理程序;如果是false,表示在冒泡阶段调用事件处理程序。举一个例子:
var btn = document.getElementById('button');
btn.addEventListener("click", function(){
alert(this.id);
}, false);
使用DOM2级方法添加事件处理程序的主要好处是可以添加多个事件处理程序。
var btn = document.getElementById('button');
btn.addEventListener("click", function(){
alert(this.id);
}, false);
btn.addEventListener("click", function(){
alert("hello");
}, false);
这里的按钮添加了两个事件处理程序。这两个事件处理程序会按照添加他们的顺序触发,因此首先会显示元素的id,其次会显示”hello”。
通过addEventListener()添加的事件可以通过removeEventLinstener()来移除。移除时传入的参数与添加处理程序时使用的参数相同。这也意味着通过addEventListener()添加的匿名函数将无法移除,如下面的例子:
var btn = document.getElementById('button');
btn.addEventListener("click", function(){
alert(this.id);
}, false);
//省略......
btn.removeEventListener("click", function(){ //没有用!
alert(this.id);
}, false);
虽然调用removeEventListener()时看似使用了相同的参数,实际上,第二个参数与传入addEventListener()中放入那一个是完全不同的函数。而传入removeEventListener()中的事件处理程序函数必须与传入addEventListener()中的相同。如下面的例子:
var btn = document.getElementById('button');
var handler = function(){
alert(this.id);
};
btn.addEventListener("click", handler, false);
//省略......
btn.removeEventListener("click", handler, false);
大多数情况下,都是将事件处理程序添加到事件流的冒泡阶段,这样可以最大限度地兼容各种浏览器。最好只在需要在事件到达目标之前截获它的时候将事件处理程序添加到捕获阶段,如果不是特别需要,不建议在事件捕获阶段注册事件处理程序。
IE9、Opera、Firefox、Chrome和Safair支持DOM2级事件处理程序。
(3)IE事件处理程序
IE中有两个函数attachEvent()和detachEvent()。这两个函数接受相同的两个参数:事件处理程序名称与事件处理程序函数。由于IE8及跟早版本只支持事件冒泡,所有通过attachEvent()添加的事件处理程序都会被添加到冒泡阶段。
使用attachEvent()为按钮添加一个事件处理程序。例如:
var btn = document.getElementById('button');
btn.attachEvent("onclick", function(){
alert('click');
});
注:attachEvent()函数的第一个参数是”onclick”,而非DOM的addEventListener()方法中的click。
在IE中使用attachEvent()与使用DOM0级方法的主要区别在于事件处理程序的作用域。在使用DOM0级方法的情况下,事件处理程序会在其所属的作用域内运行,在使用attachEvent()方法的情况下,事件处理程序会在全局作用域内运行,因此this等于window。
var btn = document.getElementById('button');
btn.attachEvent("onclick", function(){
alert(this === window); //true
});
在编写跨浏览器的代码时,要牢记这一点。
与addEventListener()类似,attachEvent()方法也可以用来为一个元素添加多个事件处理程序。
var btn = document.getElementById('button');
btn.attachEvent("onclick", function(){
alert("click"); //true
});
btn.attachEvent("onclick", function(){
alert("hello");
});
这里调用了两次attachEvent(),为同一个按钮添加了两个不同的事件处理程序。不过,与DOM方法不同的是,这些事件处理程序不是以添加它们的顺序执行,而是以相反的顺序触发。上面的例子执行的结果是先看到”hello”,然后才是”click”。
使用attachEvent()添加的事件可以通过detachEvent()来移除,条件是必须提供相同的参数。与DOM方法一样,这也意味着添加的匿名函数将不能被移除。不过,只要能够将相对的引用传给detachEvent(),就可以移除相应的事件处理程序。例如:
var btn = document.getElementById('button');
var handler = function(){
//省略......
};
btn.attachEvent("onclick", handler);
//省略......
btn.detachEvent("onclick", handler);
支持IE事件处理程序的浏览器只有IE和Opera。
(3)跨浏览器的事件处理程序
var EventUtil = {
addHandler: function(element, type, handler){
if (element.addEventListener) {
element.addEventListener(type, handler, false);
} else if (element.attachEvent) {
element.attachEvent("on" + type, handler);
} else {
element["on" + type] = handler;
}
},
removeHandler: function(){
if (element.removeListener) {
element.removeListener(type, handler, false);
} else if (element.detachEvent) {
element.detachEvent("on" + type, handler);
} else {
element["on" + type] = null;
}
}
};
这两个方法首先会检测传入的元素中是否存在DOM2级方法。如果存在DOM2级方法,则使用该方法。如果存在的是IE的方法,则采取第二种方案。
使用EventUtil对象
var btn = document.getElementById('button');
var handler = function(){
//省略......
};
EventUtil.addHandler(btn, "click", handler);
//省略......
EventUtil.removeHandler(btn, "click", handler);
addHandler()和removeHandler()没有考虑到所有的浏览器问题,例如在IE中的作用域问题。不给,使用它们添加和移除事件处理程序还是足够了。此外,DOM0级对每个事件只支持一个事件处理程序。
参考资料:《JavaScript高级程序设计》(第三版)
(完)