事件处理贯穿前端开发的方方面面:从按钮点击、表单输入,到列表滚动、模块通信,良好的事件设计直接影响到代码的可维护性与用户体验。本文聚焦 DOM 事件基础与事件流机制,帮助你在实际项目中写出既稳又优的交互逻辑。
事件是用户或浏览器触发的特定动作,例如:点击 click、鼠标移动 mousemove、页面加载 load、输入变化 input 等。通过监听事件并编写处理函数,页面就能对这些动作做出响应。
<button onclick="handleClick()">点击我</button>
<script>
function handleClick() {
alert('按钮被点击了!');
}
</script>
缺点:HTML 与 JavaScript 强耦合,不利于维护与复用。
<button id="myButton">点击我</button>
<script>
const button = document.getElementById('myButton');
button.onclick = function() {
alert('按钮被点击了!');
};
</script>
缺点:同一事件只能绑定一个处理函数。
<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:
<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('链接被点击,但不会跳转');
});