本文系统梳理JavaScript事件处理机制,涵盖鼠标交互事件的触发顺序、跨浏览器的事件注册兼容方案(addEventListener与attachEvent)、事件对象获取及默认行为与冒泡的阻止方法,并深入解析利用事件冒泡原理实现高效的事件委托技术。
鼠标事件触发顺序
当鼠标指针移入目标元素区域时,系统首先触发 mouseover 事件。
随后,若鼠标指针在元素内部持续移动,则会连续触发 mousemove 事件。
当用户按下鼠标按键(包括左键、右键或滚轮)时,触发 mousedown 事件。
当用户松开鼠标按键时,触发 mouseup 事件。
当目标元素的滚动条发生位置变化(例如通过滚轮滚动或拖动滚动条)时,触发 scroll 事件。
滚动鼠标滚轮时会触发 mousewheel 事件,需注意该事件与 scroll 事件的区别。
当鼠标指针移出目标元素区域时,触发 mouseout 事件。
事件注册
事件注册与兼容处理
在常规开发中,我们通常通过 dom.onxxxx=function(){} 的形式来绑定事件。
这种赋值方式实际上是给元素的 onxxxx 属性赋值,因此每个事件只能绑定一个处理函数。
然而在实际应用中,往往需要为一个事件绑定多个处理函数,甚至需要动态地添加或移除特定的处理函数。
采用以下事件注册方式可以解决上述需求。
首先介绍四个关键方法
具体实现代码如下:
// 非IE浏览器标准
target.addEventListener(type, listener, useCapture)
target.removeEventListener(type, listener, useCapture);
target 参数指代文档节点、document、window 或 XMLHttpRequest 对象。
type 参数为字符串类型的事件名称,不包含“on”前缀,例如“click”、“mouseover”、“keydown”等。
listener 参数为实现了 EventListener 接口的对象或 JavaScript 函数。
useCapture 参数为布尔值,表示是否使用捕获阶段,通常设置为 false。
// IE浏览器特有方式
target.attachEvent(type, listener);
target.detachEvent(type, listener);
target 参数同样指代文档节点、document、window 或 XMLHttpRequest 对象。
type 参数为字符串类型的事件名称,必须包含“on”前缀,例如“onclick”、“onmouseover”、“onkeydown”等。
listener 参数为实现了 EventListener 接口的对象或 JavaScript 函数。
这两种方式的主要区别在于执行优先级不同,具体实例如下:
ele.attachEvent("onclick", method1);
ele.attachEvent("onclick", method2);
ele.attachEvent("onclick", method3);
此时执行顺序为 method3 -> method2 -> method1。
ele.addEventListener("click", method1, false);
ele.addEventListener("click", method2, false);
ele.addEventListener("click", method3, false);
此时执行顺序为 method1 -> method2 -> method3。
以下是兼容多种浏览器的封装方法:
var func = function(){};
// 示例:addEvent(window, "load", func)
function addEvent(elem, type, fn) {
if (elem.attachEvent) {
elem.attachEvent('on' + type, fn);
return;
}
if (elem.addEventListener) {
elem.addEventListener(type, fn, false);
}
}
// 示例:removeEvent(window, "load", func)
function removeEvent(elem, type, fn) {
if (elem.detachEvent) {
elem.detachEvent('on' + type, fn);
return;
}
if (elem.removeEventListener) {
elem.removeEventListener(type, fn, false);
}
} 获取事件对象和事件源(触发事件的元素)
事件对象与源获取
具体代码如下:
function eventHandler(e){
// 获取事件对象
e = e || window.event;// 在IE和Chrome中为window.event,在FF中为e
// 获取事件源
var target = e.target || e.srcElement;// 在IE和Chrome中为srcElement,在FF中为target
} 取消事件默认行为(例如点击 <a> 标签后不跳转页面,而是执行自定义函数)
默认行为与冒泡阻止
具体代码如下:
function eventHandler(e) {
e = e || window.event;
// 阻止默认行为
if (e.preventDefault) {
e.preventDefault();// 非IE浏览器
} else {
e.returnValue = false;// IE浏览器
// 注意:此处不能使用 return false 替代
// return false 仅能取消元素默认行为
}
} 阻止事件冒泡
具体代码如下:
function myParagraphEventHandler(e) {
e = e || window.event;
if (e.stopPropagation) {
e.stopPropagation();// 非IE浏览器
} else {
e.cancelBubble = true;// IE浏览器
}
} 事件委托
事件委托应用
例如,若在一个包含大量行的表格中,为每个 <tr> 单独绑定点击事件,会引发严重的性能问题。因此,业界常用的做法是采用事件委托。
事件委托的核心思想是将事件绑定在父容器元素上,通过判断点击的子元素(target)类型来触发相应的事件处理逻辑。
事件委托依赖于事件冒泡机制,如果事件在冒泡至 table 之前被阻止,则以下代码无法正常工作。
具体代码如下:
myTable.onclick = function () {
e = e || window.event;
var targetNode = e.target || e.srcElement;
// 判断点击的是否为 TR 元素
if (targetNode.nodeName.toLowerCase() === 'tr') {
alert('You clicked a table row!');
}
} 事件流与模型
事件(Event)知识整理(二)
事件流
DOM 标准同时支持两种事件模型:捕获型事件和冒泡型事件。
当事件发生时,通常会经历捕获阶段、处理阶段和冒泡阶段(部分旧版浏览器可能不支持捕获阶段)。
捕获阶段是从上层元素向下层元素依次传递;而冒泡阶段则方向相反。
参考下图展示的流程

当事件触发时,body 会最先接收到事件信息,随后依次向下传递,直至到达最具体的目标元素,这一过程即为事件捕获阶段。
回顾事件注册方法 ele.addEventListener(type, handler, flag),其中 flag 为布尔值,true 表示在捕获阶段执行,false 表示在冒泡阶段执行。
接下来是事件冒泡阶段,从下往上依次执行已注册的事件处理函数(前提是当前元素已注册了该事件句柄)。
在此过程中,可以阻止事件继续冒泡,即停止向上传递。
阻止冒泡在特定场景下非常必要,例如:
具体代码如下:
<div onclick=funcA()>
<button onclick=funcB()>Click</button>
</div> 开发者的本意是点击 div 中按钮以外的区域时执行 funcA,点击 button 时执行 funcB。但实际点击 button 时,会先后执行 funcB 和 funcA。
如果在 button 的事件处理函数中阻止了冒泡,div 的事件句柄将不再执行。