本文详细解析JavaScript中的事件处理机制,涵盖事件流、三种事件模型及DOM2级标准。重点对比了标准浏览器与IE的事件处理程序差异,包括参数、作用域及执行顺序,并提供了基于John Resig思路的跨浏览器事件处理与移除方案,确保代码在不同环境下的稳定性。
JavaScript事件处理机制概述
JavaScript与HTML的交互核心在于事件机制。JavaScript采用异步事件驱动编程模型,当文档、浏览器、元素或相关对象发生特定情况时,浏览器会生成相应事件。若JavaScript需要关注特定类型的事件,可注册相应的处理句柄,以便在事件发生时执行预设逻辑。
事件流与模型
事件流
事件流定义了页面接收事件的顺序。以嵌套的两个div为例,点击内层div时,究竟是内层先触发click事件还是外层先触发?目前业界主要存在三种不同的事件模型。
IE的事件冒泡机制:事件最初由最具体的元素接收,随后逐级向上传播至不太具体的外层元素。
Netscape的事件捕获机制:与冒泡相反,不太具体的节点更早接收事件,而最具体的元素最后接收事件。
DOM事件流:DOM2级事件规范规定事件流包含三个阶段:事件捕获阶段、处于目标阶段和事件冒泡阶段。首先发生的是事件捕获,为拦截事件提供机会;接着是实际目标接收事件;最后是冒泡阶段。
Opera、Firefox、Chrome、Safari等主流浏览器均支持DOM事件流,而IE不支持完整的事件流,仅支持事件冒泡。
如有以下html,点击div区域
代码如下:
<!DOCTYPE html >
<html>
<head>
<meta http-equiv="Content-type" content="text/html; charset=utf-8" />
<title>Test Page</title>
</head>
<body>
<div>
Click Here</div>
</body>
</html>

事件处理程序定义
事件处理程序(handler)
事件处理程序也被称为事件侦听器(listener)。事件是指用户或浏览器自身执行的动作,如click、load、mouseover等,这些被称为事件类型(即事件名称)。响应特定事件的方法称为事件处理程序、事件监听器或事件句柄,其命名规则通常为“on”加上事件类型。
了解了基本概念后,我们来看看如何为元素添加事件处理程序。
HTML事件处理程序允许使用与事件处理程序同名的HTML属性来指定每个事件支持的相应处理程序。该属性的值应为可执行的JavaScript代码。例如,我们可以为一个button添加click事件处理程序。
代码如下:
<input type="button" value="Click Here" onclick="alert('Clicked!');" />在HTML事件处理程序中,可以直接包含要执行的具体动作,也可以调用页面其他位置定义的脚本。例如,刚才的例子可以改写为调用外部函数。
代码如下:
<input type="button" value="Click Here" onclick="showMessage();" />
<script type="text/javascript">
function showMessage() {
alert('Clicked!');
}在HTML中指定事件处理程序虽然书写方便,但存在两个主要缺点。
首先,存在加载顺序问题。如果事件处理程序在HTML代码之后加载,用户可能在处理程序尚未加载完成时就点击按钮触发事件,从而产生时间差导致错误。
其次,这种写法使HTML代码与JavaScript代码紧密耦合,不利于后期维护。
JavaScript指定事件处理程序
通过JavaScript指定事件处理程序,即将一个方法赋值给元素的事件处理程序属性。每个元素都拥有自己的事件处理程序属性,名称通常为小写(如onclick)。将这些属性的值设置为一个方法,即可指定事件处理程序,如下所示。
代码如下:
<input id="btnClick" type="button" value="Click Here" />
<script type="text/javascript">
var btnClick = document.getElementById('btnClick');
btnClick.onclick = function showMessage() {
alert(this.id);
};
</script>采用这种方式,事件处理程序被视为元素的方法,在元素的作用域下运行,此时this指向当前元素。因此,点击button后的结果是:btnClick。
这种方式还有一个好处,即我们可以删除事件处理程序,只需将元素的onclick属性赋值为null即可。
DOM2事件处理程序
DOM2事件处理程序:DOM2级事件定义了两个用于处理和删除事件处理程序的操作方法:addEventListener和removeEventListener。所有DOM节点都包含这两个方法,且它们均接受三个参数:事件类型、事件处理方法以及一个布尔值。若最后一个布尔参数为true,表示在捕获阶段调用事件处理程序;若为false,则在事件冒泡阶段处理。
针对刚才的例子,我们可以这样编写代码。
代码如下:
<input id="btnClick" type="button" value="Click Here" />
<script type="text/javascript">
var btnClick = document.getElementById('btnClick');
btnClick.addEventListener('click', function() {
alert(this.id);
}, false);
</script>上述代码为button添加了click事件的处理程序,并在冒泡阶段触发。与前一种方法类似,该程序也在元素的作用域下运行。不过,此方法的一个优势是,我们可以为click事件添加多个处理程序。
代码如下:
<input id="btnClick" type="button" value="Click Here" />
<script type="text/javascript">
var btnClick = document.getElementById('btnClick');
btnClick.addEventListener('click', function() {
alert(this.id);
}, false);
btnClick.addEventListener('click', function() {
alert('Hello!');
}, false);
</script>这样,两个事件处理程序会在用户点击button后按照添加顺序依次执行。
通过addEventListener添加的事件处理程序只能通过removeEventListener移除,且移除时的参数必须与添加时完全相同。这意味着,之前添加的匿名函数无法被移除,因为虽然方法体相同,但句柄(引用)不同。因此,当我们需要移除事件处理程序时,应如下编写代码。
代码如下:
<input id="btnClick" type="button" value="Click Here" />
<script type="text/javascript">
var btnClick = document.getElementById('btnClick');
var handler=function() {
alert(this.id);
}
btnClick.addEventListener('click', handler, false);
btnClick.removeEventListener('click', handler, false);
</script>IE事件处理程序与兼容性问题
接下来讨论老生常谈的IE兼容性问题。
IE并不支持addEventListener和removeEventListener方法,而是实现了两个类似的方法:attachEvent和detachEvent。这两个方法均接收两个相同的参数:事件处理程序名称和事件处理程序方法。由于IE仅支持事件冒泡,因此添加的程序会被添加到冒泡阶段。
使用attachEvent添加事件处理程序可如下编写。
代码如下:
<input id="btnClick" type="button" value="Click Here" />
<script type="text/javascript">
var btnClick = document.getElementById('btnClick');
var handler=function() {
alert(this.id);
}
btnClick.attachEvent('onclick' , handler);
</script>结果是undefined,这看起来很奇怪,稍后我们会对此进行解释。
使用attachEvent添加的事件处理程序可以通过detachEvent移除,条件同样是参数相同,且匿名函数不能被移除。
代码如下:
<input id="btnClick" type="button" value="Click Here" />
<script type="text/javascript">
var btnClick = document.getElementById('btnClick');
var handler=function() {
alert(this.id);
}
btnClick.attachEvent('onclick', handler);
btnClick.detachEvent('onclick', handler);
</script>跨浏览器的事件处理程序
跨浏览器事件处理程序实现
通过前面的内容我们可以看到,在不同浏览器下,添加和移除事件处理程序的方式各不相同。要编写跨浏览器的事件处理程序,首先必须了解不同浏览器在处理事件处理程序时的区别。
在添加事件处理程序时,addEventListener和attachEvent主要有以下几个区别: 1. 参数个数不相同,这是最直观的区别。addEventListener有三个参数,而attachEvent只有两个。由于attachEvent添加的事件处理程序只能发生在冒泡阶段,而addEventListener的第三个参数可以决定添加的事件处理程序是在捕获阶段还是冒泡阶段处理(我们通常为了浏览器兼容性都设置为冒泡阶段)。
2. 第一个参数意义不同。addEventListener的第一个参数是事件类型(如click、load),而attachEvent的第一个参数指明的是事件处理函数名称(如onclick、onload)。
3. 事件处理程序的作用域不相同。addEventListener的作用域是元素本身,this指的是触发元素;而attachEvent的事件处理程序会在全局变量内运行,this是window对象,因此刚才的例子才会返回undefined,而不是元素id。
4. 为一个事件添加多个事件处理程序时,执行顺序不同。addEventListener添加的事件会按照添加顺序执行;而attachEvent添加多个事件处理程序时,执行顺序无规律(添加的方法少的时候大多是按添加顺序的反顺序执行的,但是添加的多了就无规律了)。因此,在添加多个处理程序时,如果不依赖执行顺序则无妨;若依赖于函数执行顺序,最好自行处理,不要指望浏览器。
了解了这四点区别后,我们可以尝试编写一个浏览器兼容性较好的添加事件处理程序方法。
代码如下:
function addEvent(node, type, handler) {
if (!node) return false;
if (node.addEventListener) {
node.addEventListener(type, handler, false);
return true;
}
else if (node.attachEvent) {
node.attachEvent('on' + type, handler, );
return true;
}
return false;
}这样,首先我们解决了第一个问题(参数个数不同),现在统一为三个参数,并采用事件冒泡阶段触发。第二个问题也得以解决,如果是IE,我们给type添加上“on”。第四个问题目前还没有通用解决方案,需要用户自己注意。一般情况下,大家也不会添加很多事件处理程序,试试这个方法感觉很不错。但是我们没有解决第三个问题,由于处理程序作用域不同,如果handler内有this之类操作,那么就会出错在IE下,实际上大多数函数都会有this操作。
代码如下:
function addEvent(node, type, handler) {
if (!node) return false;
if (node.addEventListener) {
node.addEventListener(type, handler, false);
return true;
}
else if (node.attachEvent) {
node.attachEvent('on' + type, function() { handler.apply(node); });
return true;
}
return false;
}这样处理就可以解决this的作用域问题,但是新的问题又来了:我们这样等于添加了一个匿名的事件处理程序,无法用detachEvent取消事件处理程序。有很多解决方案,我们可以借鉴大师的处理方式,jQuery创始人John Resig就是这样做的。
代码如下:
function addEvent(node, type, handler) {
if (!node) return false;
if (node.addEventListener) {
node.addEventListener(type, handler, false);
return true;
}
else if (node.attachEvent) {
node['e' + type + handler] = handler;
node[type + handler] = function() {
node['e' + type + handler](window.event);
};
node.attachEvent('on' + type, node[type + handler]);
return true;
}
return false;
}在取消事件处理程序的时候,
代码如下:
function removeEvent(node, type, handler) {
if (!node) return false;
if (node.removeEventListener) {
node.removeEventListener(type, handler, false);
return true;
}
else if (node.detachEvent) {
node.detachEvent('on' + type, node[type + handler]);
node[type + handler] = null;
}
return false;
}John Resig很巧妙地利用了闭包,看起来很不错。
事件对象详解
事件对象
在触发DOM上的某个事件时,会产生一个事件对象event,该对象包含着所有与事件有关的信息,包括产生事件的元素、事件类型等相关信息。所有浏览器都支持event对象,但支持方式不同。
DOM中的事件对象:兼容DOM的浏览器会产生一个event对象传入事件处理程序中。应用一下刚才我们写的addEvent方法。
代码如下:
var btnClick = document.getElementById('btnClick');
addEvent(btnClick, 'click', handler);点击button的时候我们可以看到弹出内容是click的弹窗。
event对象包含与创建它的特定事件有关的属性和方法。触发事件的类型不同,可用的属性和方法也不同,但是所有事件都会包含。
|
属性/方法 |
类型 |
读/写 |
说明 |
| bubbles | Boolean | 只读 | 事件是否冒泡 |
| cancelable | Boolean | 只读 | 是否可以取消事件的默认行为 |
| currentTarget | Element | 只读 | 事件处理程序当前处理元素 |
| detail | Integer | 只读 | 与事件相关细节信息 |
| eventPhase | Integer | 只读 | 事件处理程序阶段:1 捕获阶段,2 处于目标阶段,3 冒泡阶段 |
| preventDefault() | Function | 只读 | 取消事件默认行为 |
| stopPropagation() | Function | 只读 | 取消事件进一步捕获或冒泡 |
| target | Element | 只读 | 事件的目标元素 |
| type | String | 只读 | 被触发的事件类型 |
| view | AbstractView | 只读 | 与事件关联的抽象视图,等同于发生事件的window对象 |
在事件处理程序内部,this始终等同于currentTarget,而target是事件的实际目标。
要阻止事件的默认行为,可以使用preventDefault()方法,前提是cancelable值为true。例如,我们可以阻止链接导航这一默认行为。
代码如下:
document.getElementsByTagName('a').onclick = function (e) {
e.preventDefault();
}stopPaopagation()方法可以停止事件在DOM层次的传播,即取消进一步的事件捕获或冒泡。我们可以在button的事件处理程序中调用stopPropagation()从而避免注册在body上的事件发生。
代码如下:
var handler = function (e) {
alert(e.type);
e.stopPropagation();
}
addEvent(document.body, 'click', function () { alert('Clicked body')});
var btnClick = document.getElementById('btnClick');
addEvent(btnClick, 'click', handler);若是注释掉e.stopPropagation(); 在点击button的时候,由于事件冒泡,body的click事件也会触发,但是调用这句后,事件会停止传播。
IE中的事件对象访问:IE中的event对象有几种不同的方式,取决于指定事件处理程序的方法。直接为DOM元素添加事件处理程序时,event对象作为window对象的一个属性存在。
代码如下:
var handler = function () {
var e = window.event;
alert(e.type);
}
var btnClick = document.getElementById('btnClick');
btnClick.onclick = handler;我们通过window.event取得了event对象,并检测到了其类型。可是如果事件处理程序是通过attachEvent添加的,那么就会有一个event对象被传入事件处理程序中。
代码如下:
var handler = function (e) {
alert(e.type);
}
var btnClick = document.getElementById('btnClick');
attachEvent(btnClick, handler);当然这时候也可以通过window对象访问event,方便起见,我们一般会传入event对象。IE中所有的事件都包含以下属性方法。
|
属性/方法 |
类型 |
读/写 |
说明 |
| cancelBulle | Boolean | 读/写 | 默认为false,设置为true后可以取消事件冒泡 |
| returnValue | Boolean | 读/写 | 默认为true,设为false可以取消事件默认行为 |
| srcElement | Element | 只读 | 事件的目标元素 |
| type | String | 只读 | 被触发的事件类型 |
跨浏览器的事件对象:虽然DOM和IE的event对象不同,但基于它们的相似性,我们还是可以写出跨浏览器的事件对象方案。
代码如下:
function getEvent(e) {
return e || window.event;
}
function getTarget(e) {
return e.target || e.scrElement;
}
function preventDefault(e) {
if (e.preventDefault)
e.preventDefault();
else
e.returnValue = false;
}
function stopPropagation(e) {
if (e.stopPropagation)
e.stopPropagation();
else
e.cancelBubble = true;
}常用HTML事件
常用HTML事件列表
有一些HTML事件我们会经常用到,这些事件不一定与用户操作有关,这里只是简单提及,详细用法大家就得百度谷歌了。
1.load:当页面完全加载后在window上触发,当图像加载完成后在img元素上触发,或当嵌入内容加载完成时,在object元素上触发 2.unload:页面完全卸载后在window上触发,或嵌入内容卸载后在object元素触发 3.select:用户选择文本框中的字符时触发 4.change:文本框焦点变化后其值改变时触发 5.submit:用户提交表单的时候触发 6.resize:窗口或框架大小变化的时候在window上触发 7.scrool:用户滚动带滚动条的元素时,在该元素上触发 8.focus:页面或元素获得焦点时在window及相应元素上触发 9.blur:页面或元素失去焦点时在window及相应元素上触发 10.beforeunload:页面卸载前在window上触发 11.mousewheel:不算HTML的,当用户通过鼠标滚轮与页面交互,在垂直方向滚动页面时触发