「原创声明:保留所有权利,禁止转载」
前言
事件处理贯穿前端开发的方方面面:从按钮点击、表单输入,到列表滚动、模块通信,良好的事件设计直接影响到代码的可维护性与用户体验。本文聚焦 DOM 事件基础与事件流机制,帮助你在实际项目中写出既稳又优的交互逻辑。
读者定位与阅读收获
- 面向:入门到中级前端工程师,追求工程化与可维护性
- 收获:弄清捕获与冒泡差异、正确移除监听器、规避常见陷阱
DOM 事件基础
什么是事件
事件是用户或浏览器触发的特定动作,例如:点击 click、鼠标移动 mousemove、页面加载 load、输入变化 input 等。通过监听事件并编写处理函数,页面就能对这些动作做出响应。
监听事件的三种方式
方式一:HTML 内联(不推荐)
<button onclick="handleClick()">点击我</button>
<script>
function handleClick() {
alert('按钮被点击了!');
}
</script>
缺点:HTML 与 JavaScript 强耦合,不利于维护与复用。
方式二:DOM 属性
<button id="myButton">点击我</button>
<script>
const button = document.getElementById('myButton');
button.onclick = function() {
alert('按钮被点击了!');
};
</script>
缺点:同一事件只能绑定一个处理函数。
方式三:addEventListener(推荐)
<button id="myButton">点击我</button>
<script>
const button = document.getElementById('myButton');
// 可以绑定多个处理函数
button.addEventListener('click', function() {
console.log('第一个处理函数');
});
button.addEventListener('click', function() {
console.log('第二个处理函数');
});
</script>
优点:
- 可绑定多个处理函数
- 可控制触发阶段(捕获或冒泡)
- 可精确移除监听器
移除事件监听
function handleClick() {
console.log('点击了!');
}
const button = document.getElementById('myButton');
button.addEventListener('click', handleClick);
// 移除监听(必须使用同一函数引用)
button.removeEventListener('click', handleClick);
常见错误:使用匿名函数导致无法移除。
// ❌ 错误:无法移除匿名函数
button.addEventListener('click', function() {
console.log('点击了!');
});
button.removeEventListener('click', function() {
console.log('点击了!');
});
// ✅ 正确:使用命名函数或保存函数引用
const handler = function() {
console.log('点击了!');
};
button.addEventListener('click', handler);
button.removeEventListener('click', handler);
事件冒泡与捕获
事件流的三个阶段
当一个事件发生时,它会经历三个阶段:
1. 捕获阶段:从 window 向下传播到目标元素
2. 目标阶段:到达目标元素
3. 冒泡阶段:从目标元素向上传播到 window
基础 API:
- addEventListener(type, listener, useCapture):第三个参数为 true 表示在捕获阶段触发,默认 false 表示在冒泡阶段触发
- removeEventListener(type, listener, useCapture):移除监听时参数需与添加时完全一致 提示:移除监听器时,类型、回调函数引用与第三个参数必须与添加时严格一致,否则无法移除。
实例演示
<div id="outer" style="padding: 50px; background: lightblue;">
外层 DIV
<div id="inner" style="padding: 30px; background: lightcoral;">
内层 DIV
<button id="button">点击我</button>
</div>
</div>
<script>
const outer = document.getElementById('outer');
const inner = document.getElementById('inner');
const button = document.getElementById('button');
// 冒泡阶段(默认)
button.addEventListener('click', () => {
console.log('按钮 - 冒泡');
});
inner.addEventListener('click', () => {
console.log('内层 DIV - 冒泡');
});
outer.addEventListener('click', () => {
console.log('外层 DIV - 冒泡');
});
// 捕获阶段(第三个参数设为 true)
outer.addEventListener('click', () => {
console.log('外层 DIV - 捕获');
}, true);
inner.addEventListener('click', () => {
console.log('内层 DIV - 捕获');
}, true);
button.addEventListener('click', () => {
console.log('按钮 - 捕获');
}, true);
// 点击按钮时,典型输出顺序:
// 外层 DIV - 捕获
// 内层 DIV - 捕获
// 按钮 - 捕获
// 按钮 - 冒泡
// 内层 DIV - 冒泡
// 外层 DIV - 冒泡
</script>
阻止传播与默认行为
// 阻止冒泡传播
button.addEventListener('click', (e) => {
console.log('按钮被点击');
e.stopPropagation();
});
// 阻止同一元素其他监听器
button.addEventListener('click', (e) => {
console.log('第一个监听器');
e.stopImmediatePropagation();
});
button.addEventListener('click', () => {
console.log('第二个监听器 - 不会执行');
});
// 阻止默认行为(如链接跳转)
const link = document.querySelector('a');
link.addEventListener('click', (e) => {
e.preventDefault();
console.log('链接被点击,但不会跳转');
});
适用场景与建议
- 复杂嵌套交互:合理使用捕获/冒泡,避免在深层节点重复绑定监听
- 列表或表格点击:优先使用事件委托,降低绑定与内存开销
- 组件封装:在组件根节点统一处理事件,对外仅暴露清晰回调
易错点清单
- 使用匿名函数绑定导致无法 removeEventListener
- 循环中使用 var 绑定事件引发索引错误
- 误用 stopImmediatePropagation 影响同元素其他监听器
- 忘记销毁时清理监听,造成内存泄漏
FunTester 原创精华
TesterHome 为用户提供「保留所有权利,禁止转载」的选项。
除非获得原作者的单独授权,任何第三方不得转载标注了「原创声明:保留所有权利,禁止转载」的内容,否则均视为侵权。
具体请参见TesterHome 知识产权保护协议。
如果觉得我的文章对您有用,请随意打赏。您的支持将鼓励我继续创作!
暫無回覆。