JavaScript 事件机制详解:从基础到事件委托与冒泡

作者:流萤时间:2026-10-26 20:47:46浏览:410

本文系统梳理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 的事件句柄将不再执行。

作者最新文章