17.1 事件流
事件流描述的是页面接收事件的顺序机制。在DOM标准中,事件的传播过程被划分为不同的阶段,以便开发者更精确地控制事件的触发时机。
DOM事件流的三个阶段
事件捕获阶段:事件从文档根节点开始,逐层向下传递,直至达到目标元素。
window
目标阶段:事件成功抵达绑定该事件的目标元素,此时可以执行相应的处理逻辑。
事件冒泡阶段:事件从目标元素开始,沿DOM树向上传播,最终回到文档根节点。
window
现代实践与深入理解
useCapture 参数的应用:在使用 addEventListener 方法时,第三个参数可传入布尔值或选项对象。若传入 true,表示事件处理程序将在捕获阶段被调用。这一特性常用于需要优先拦截事件的场景。
addEventListener
true
例如,在实现一个可拖拽的模态框时,可以在 document 或外层容器的捕获阶段监听鼠标按下事件(mousedown),从而确保即使子元素调用了 stopPropagation() 阻止冒泡,依然能够正确捕获到拖拽起始动作。
document
mousedown
事件委托的核心原理:事件冒泡是实现“事件委托”技术的基础。通过将多个子元素的事件监听器统一绑定到其父级或更高层级的祖先元素上,可以显著提升性能和维护性。
// 现代框架中,这样的逻辑常写在useEffect或onMounted中
useEffect(() => {
const handleMouseDown = (event) => {
// 检查event.target,判断是否在拖拽手柄上
if (event.target.closest('.drag-handle')) {
// 开始拖拽逻辑
startDrag(event);
}
};
// 使用捕获阶段
document.addEventListener('mousedown', handleMouseDown, true);
return () => {
document.removeEventListener('mousedown', handleMouseDown, true);
};
}, []);
这种模式的优势在于:一方面减少了重复的事件监听器数量,节省内存;另一方面,对于动态添加的子元素,无需重新绑定事件,系统仍能正常响应。因此,事件委托已成为现代高性能Web应用开发中的关键技术之一。
<ul id="todo-list">
<li>任务1 <button class="delete">删除</button></li>
<li>任务2 <button class="delete">删除</button></li>
<!-- ... 动态添加的li ... -->
</ul>
<script>
const list = document.getElementById('todo-list');
// 委托给父元素ul
list.addEventListener('click', (event) => {
// 利用event.target精准定位
if (event.target.classList.contains('delete')) {
// 删除对应的li
const li = event.target.closest('li');
li.remove();
}
});
</script>
17.2 事件处理程序
事件是指由用户行为或浏览器自身触发的动作,如点击(click)、页面加载完成(load)、鼠标移入(mouseover)等。为了响应这些事件而执行的函数,被称为事件处理程序,也称为事件监听器。这类函数的命名通常以 "on" 开头,例如 onclick、onload 等。
17.2.1 HTML事件处理程序
可以通过HTML标签的属性来直接指定某个事件的处理逻辑。每个支持事件的元素都可以使用对应的事件属性(如 onclick)并赋予一段JavaScript代码作为值。
需要注意的是,当在HTML中编写JavaScript代码时,应正确转义特殊字符,避免语法错误。此外,这种方式允许直接嵌入操作指令,也可以调用外部定义好的脚本函数。
<input type="button" value="Click Me" onclick="console.log('clicked')"/>
//使用双引号时转义
<input type="button" value="Click Me" onclick="console.log("clicked")"/>
//使用外部定义脚本
<script>
function showMessage(){
console.log("Hello World");
}
</script>
<input type="button" value="Click Me" onclick="showMessage()"/>
17.2.2 DOM Level 0 事件处理程序
这是最早期的一种事件绑定方式,起源于第四代Web浏览器。其核心做法是将一个函数赋值给DOM元素的事件属性,例如:element.onclick = function()。
尽管简单易用,但该方法存在明显局限:同一事件只能绑定一个处理函数,且无法选择在捕获或冒泡阶段执行。
//先从文档中获取元素
let btn = document.getElementById("myBtn");
//再为元素属性赋值为函数
btn.onclick = function(){
console.log(this.id); // "myBtn"
}
//可以通过将时间处理程序值null完成移除:btn.onclick = null
17.2.3 DOM Level 2 事件处理程序
DOM2 Events规范引入了标准化的方法来添加和移除事件监听器:addEventListener() 和 removeEventListener()。这两个方法存在于所有DOM节点上,接受三个参数:
- 事件类型名称(如 'click')
- 回调函数
- 一个布尔值或配置对象:true 表示在捕获阶段执行,false(默认)则在冒泡阶段执行
addEventListener()
removeEventListener()
这种方法支持为同一事件注册多个监听器,并能明确控制执行阶段,具备良好的灵活性和可维护性。
let btn = document.getElementById("myBtn");
let handler = function() {
console.log(this.id);
}
btn.addEventListener("click",()=>{
console.log(this.id);
},false);
//允许添加多个事件处理程序,按添加顺序执行
btn.addEventListener("click",handler,false);
//不允许添加一个匿名函数作为事件处理程序
btn.addEventListener("click",function(){ //不执行
console.log(this.id);
},false);
//通过addEventListener添加的事件处理程序只能通过removeEventListener()传入相同的参数来移除
btn.removeEventListener("click",handler,false);
17.2.4 IE专有事件处理程序
在IE8及更早版本中,采用的是非标准的 attachEvent() 和 detachEvent() 方法。它们均接收两个参数:事件名(带'on'前缀,如'onclick')和处理函数。
由于这些旧版IE仅支持事件冒泡,所有通过 attachEvent() 绑定的事件都会在冒泡阶段触发。另外值得注意的是,事件处理函数在此环境下运行于全局作用域,因此其中的 this 指向 window 对象。
let btn = document.getElementById("myBtn");
let handler = function() {
console.log("Clicked");
}
btn.attachEvent("onclick",()=>{
console.log("Hello");
});
//允许添加多个事件处理程序,按添加顺序执行
btn.attachEvent("onclick",handler,false);
//通过attachEvent添加的事件处理程序只能通过detachEvent()传入相同的参数来移除
btn.detachEvent("onclick",handler);
现代开发建议与扩展
推荐使用 addEventListener:在当前前端开发实践中,必须优先选用 addEventListener。它不仅支持事件捕获、允许多个监听器共存,还能通过 removeEventListener 精确解绑,避免内存泄漏。
相比之下,HTML内联方式和DOM 0级绑定因存在污染全局、无法捕获、单监听等缺陷,已被视为过时方案,仅见于遗留系统或特定调试场景。
options 参数详解:现代浏览器对 addEventListener 的第三个参数进行了增强,支持传入一个配置对象,提供更精细的控制能力:
capture: true—— 等效于原先的布尔值true,表示在捕获阶段触发。
capture: Boolean
useCapture
once: true —— 监听器仅执行一次,之后自动移除。适用于“首次点击即生效”的交互设计。once: Boolean
passive: true —— 明确告知浏览器该监听器不会调用 preventDefault()。passive: Boolean
preventDefault()
此设置对移动端尤其重要,例如在滚动(scroll)或触摸(touchstart)事件中启用 passive 可让浏览器立即响应滑动操作,极大提升滚动流畅度。
touchmove
signal —— 接收一个 AbortSignal 对象,可通过调用 AbortController 的 abort() 方法来统一取消监听器。signal: AbortSignal
AbortController
controller.abort()
这一机制广泛应用于现代框架(如React)中,实现组件卸载时自动清理事件订阅,是一种优雅且可靠的资源管理方式。
// 现代事件监听示例
const controller = new AbortController();
const { signal } = controller;
element.addEventListener('touchmove', (e) => {
// 因为passive: true, 这里调用preventDefault()会报错
// do something without blocking scroll
}, { passive: true });
button.addEventListener('click', () => {
console.log('只会执行一次!');
}, { once: true });
// 在React useEffect或组件卸载时
useEffect(() => {
window.addEventListener('resize', handleResize, { signal });
return () => {
// 旧的清理方式:window.removeEventListener('resize', handleResize);
// 新的方式:直接abort()
controller.abort();
};
}, []);
17.3 事件对象
当DOM中发生事件时,系统会自动生成一个包含全部相关信息的对象——event 对象。该对象作为参数传递给事件处理函数,封装了事件的类型、触发源、鼠标坐标等关键数据。
例如,由鼠标操作引发的事件会在 event 对象中包含 clientX、clientY 等表示位置的信息。
17.3.1 DOM事件对象结构
在符合DOM标准的浏览器中,事件处理函数接收到的第一个参数就是 event 对象。所有事件类型的 event 对象都共享以下通用属性和方法:
let btn = document.getElementById("myBtn");
//DOM0事件处理程序支持
btn.onclick = function(event){
console.log(event.type); //"click"
}
//DOM2事件处理程序支持
btn.addEventListener("click",(event) => {
console.log(event.type); //"click"
},false);
| 属性/方法 | 类型 | 读/写 | 说明 |
|---|---|---|---|
| bubbles | 布尔值 | 只读 | 指示该事件是否支持冒泡 |
| cancelable | 布尔值 | 只读 | 表示是否可通过 preventDefault() 取消事件的默认行为 |
| currentTarget | 元素 | 只读 | 当前正在执行事件处理程序的DOM元素(即绑定该监听器的元素) |
| defaultPrevented | 布尔值 | 只读 | 表示是否已调用 preventDefault() 方法 |
true 表示 preventDefault() 方法已被调用(该特性在 DOM3 Events 中新增)。
detail:整数类型,只读属性,用于表示与事件相关的额外信息。
eventPhase:只读的整数,指示当前事件处理程序所处的阶段。其中,1 表示捕获阶段,2 表示事件到达目标,3 表示冒泡阶段。
preventDefault():只读函数,用于取消事件的默认行为,但前提是 cancelable 属性为 true。例如,可以阻止链接点击后的页面跳转。
stopImmediatePropagation():只读方法,能够阻止后续所有事件捕获或冒泡,并且停止其他事件处理程序的执行(DOM3 Events 新增功能)。
stopPropagation():只读函数,用于阻止事件继续向上冒泡或向下捕获,仅在 bubbles 属性为 true 时有效。
target:只读属性,表示触发事件的那个元素节点。
trusted:布尔值,只读。若为 true,说明事件由浏览器自动生成;false 则表示由开发者通过 JavaScript 手动创建(DOM3 Events 新增)。
type:字符串类型,只读,表示当前被触发的事件类型名称。
view:只读属性,返回与事件关联的抽象视图对象,通常等同于事件发生时所在的 window 对象。
17.4 事件类型
Web 浏览器支持多种类型的事件。根据 DOM3 Events 规范,主要包括以下几类:
- 用户界面事件 (UIEvent):与浏览器窗口或表单交互相关的一般性事件,不依赖特定输入设备。
- 焦点事件 (FocusEvent):当元素获取或失去焦点时被触发。
- 鼠标事件 (MouseEvent):由鼠标操作引发的事件。
- 滚轮事件 (WheelEvent):使用鼠标滚轮或其他类似输入设备时触发。
- 输入事件 (InputEvent):在向文档中输入文本内容时触发。
- 键盘事件 (KeyboardEvent):通过键盘进行页面操作时触发。
- 合成事件 (CompositionEvent):在使用 IME(输入法编辑器)输入字符时触发。
17.4.1 用户界面事件
这类事件主要涉及浏览器窗口或表单元素的交互,通常不限于用户主动操作,也不绑定特定输入方式。
1、load 事件
描述:当整个页面及其所有外部资源(如图片、JavaScript 文件和 CSS 样式表)完全加载完毕后触发。
<iframe>
<object>
典型用法1:推荐使用 addEventListener() 方法注册事件处理函数。
window.addEventListener('load', () => {
// 页面完全加载完成,可以安全地操作所有DOM元素
//支持动态检测样式表是否加载完成:
let link = document.createElement("link");
link.type = "text/css";
link.rel = "stylesheet";
linck.addEventListener("load", (event) => {
console.log("css loaded");
});
link.href = "example.css";
document.getElementsByTagName("head")[0].appendChild(link);
});
const img = document.getElementById('myImage');
img.addEventListener('load', () => {
// 图片加载完成
});
典型用法2:也可通过为元素设置 onload 属性来绑定处理逻辑。
<body onload="console.log('Loaded!')"></body>
2、unload 事件
描述:在文档被完全卸载时触发,常见于关闭标签页、刷新页面或跳转至新地址。常用于释放资源,防止内存泄漏。
注意:避免在此事件中执行阻塞操作或弹出对话框,此类行为可能被浏览器拦截。由于事件发生在页面已卸载之后,因此不应尝试访问 DOM 或修改页面外观。
典型用法1:建议采用 addEventListener() 方法添加监听器。
window.addEventListener('unload', () => {
// 页面完全卸载完成后执行
});
典型用法2:也可以通过设置元素的 onunload 属性实现绑定。
<body onunload="console.log('Unloaded!')"></body>
3、resize 事件
描述:浏览器窗口尺寸发生变化时触发。
重要提示:此事件会高频连续触发,必须结合防抖机制以提升性能表现。不同浏览器对触发时机的实现存在一定差异。
典型用法:由于 resize 事件作用于 window 对象,可通过 JavaScript 在 window 上监听,或为 <body> 元素设置 onresize 属性来响应变化。
window.addEventListener("resize", (event) => {
console.log("Resized");
})
4、scroll 事件
描述:当某个元素发生滚动时触发,适用于实现无限滚动、视差滚动等交互效果。
重要提示:与 resize 类似,scroll 也会频繁触发,强烈建议使用节流技术优化性能。
典型用法:虽然事件绑定在 window 上,但实际上反映的是页面内具体元素的滚动状态。在混杂模式下,可通过 <body> 元素检测 scrollLeft 和 scrollTop 的变化;而在标准模式下,除早期 Safari 外,这些属性的变化均体现在 <html> 元素上。
resize
window.addEventListener("scroll", (event) => {
if(document.compatMode=="CSS1Compat"){
console.log(document.documentElement.scrollTop);
}else{
console.log(document.body.scrollTop);
}
});
17.4.2 焦点事件
当页面中的元素获得或失去焦点时,将触发相应的焦点事件。
blur:
描述:元素失去焦点时触发。
此事件不会冒泡。
focus:
描述:元素获得焦点时触发。
该事件不具备冒泡特性。
focusin:
描述:元素获得焦点时触发。
此事件支持冒泡,可视为
focus
的冒泡版本。
focusout:
描述:元素失去焦点时触发。
支持冒泡机制,是
blur
的冒泡形式。
最佳实践:由于
focusin
和
focusout
具备冒泡能力,可以在父级容器上统一监听子元素的焦点变化,特别适合用于表单字段的实时验证与提示。
17.4.3 鼠标和滚轮事件
鼠标事件是最常见的交互事件类型,由鼠标设备的操作触发。
常见事件:
click
:按下主鼠标按钮(通常为左键)或键盘回车键时触发。
dblclick
:双击操作时触发。
mousedown当在元素上按下任意鼠标按钮时,会触发相应的事件。
mouseup
释放鼠标任意按键时将触发该事件。
mouseenter
当光标首次进入某个元素的可视范围内时,会触发一次事件。此事件不会冒泡。
mouseleave
当光标从元素内部移出到外部时触发。该事件同样不具备冒泡特性。
mouseover
如果光标原本位于一个元素之外,随后移动至另一个元素的边界内,此时会触发该事件。
mouseout
当鼠标指针从一个元素移动到另一个元素(包括其子元素)时触发此事件。
mousemove
只要光标在元素内部持续移动,该事件就会被反复触发。
在所有鼠标事件中,除了以下两个事件外,其余均支持事件冒泡机制:
mouseenter
mouseleave
事件对象的关键属性
clientX 与 clientY:表示鼠标事件发生时,光标相对于浏览器视口的位置坐标。
clientX, clientY
pageX 与 pageY:指示事件发生时,光标在整页文档中的位置,包含页面滚动所产生的偏移量。
pageX, pageY
screenX 与 screenY:代表事件触发时,光标相对于整个屏幕设备的坐标位置。
screenX, screenY
shiftKey、ctrlKey、altKey 和 metaKey:布尔类型的属性,用于判断事件发生时是否同时按下了对应的修饰键。
shiftKey, ctrlKey, altKey, metaKey
button:标识被按下的鼠标按键类型,其中 0 表示主按钮(通常是左键),1 表示中键(滚轮键),2 表示次按钮(右键)。需注意的是,IE8 及更早版本对此属性的定义与现代标准完全不同。
button
典型事件触发顺序
双击操作的标准事件流程如下:
mousedown →
mouseup →
click →
mousedown →
mouseup →
click →
dblclick
关于鼠标悬停行为的说明
mouseenter 和 mouseleave 不会因子元素的存在而重复触发;而 mouseover 与 mouseout 则会在进入或离开子元素时频繁触发,因此在处理复杂结构时需特别注意。
mouseenter
mouseleave
mouseover
mouseout
mousewheel 事件
描述:这是一个现代标准事件,当用户使用鼠标滚轮或其他类似输入设备进行滚动操作时触发。该事件可在任意DOM元素上发生,在IE8中会向上冒泡至document节点,在现代浏览器中则会一直冒泡至window对象。
事件对象包含的重要属性如下:
deltaX、deltaY、deltaZ:分别表示在X轴、Y轴和Z轴方向上的滚动增量。
deltaX
deltaY
deltaZ
其中,deltaY 的值为负时表示向上滚动,正值则表示向下滚动。
deltaY
deltaMode:用于说明上述delta值的计量单位,0 表示像素,1 表示行,2 表示页。
deltaMode
delta
注意事项:传统的 DOMMouseScroll 事件已被废弃,推荐使用标准化的 mousewheel 事件替代。
mousewheel
wheel
element.addEventListener('wheel', (event) => {
// 阻止默认的滚动行为
event.preventDefault();
// 根据 event.deltaY 实现自定义滚动逻辑
element.scrollTop += event.deltaY;
});
触摸屏设备的兼容性说明
由于触摸屏设备不支持传统鼠标交互,相关事件的行为有所差异:
- 不支持
dblclick事件。双指点击通常由浏览器用于页面缩放,且无法通过脚本阻止或覆盖这一默认行为。 - 单指点触可点击元素时,首先会触发
mousemove事件;若此次操作未引起内容变化,则继续依次触发mousedown、mouseup和click事件。 mousemove在触摸场景下也可能引发mouseover和mouseout的触发。- 双指点触并滑动导致页面滚动时,会同时触发
mousewheel和scroll事件。


雷达卡


京公网安备 11010802022788号







