事件

事件是用户与页面交互的桥梁:点击、输入、滚动、按键都会触发事件,JS 通过监听事件来响应用户操作。

这一篇讲事件的注册与解绑、事件类型、事件对象、事件流(冒泡/捕获)、事件委托。其中事件流和事件委托是重点,也是 JS 与 Java GUI(如 Swing 的监听器)最大的差异。

一、注册与解绑事件

1. 三种注册方式

1
2
<!-- 方式一:HTML 属性(不推荐,结构和逻辑混在一起) -->
<button onclick="alert('hi')">按钮</button>
1
2
3
4
5
6
7
8
9
10
// 方式二:DOM 属性(同一事件只能绑一个,后写覆盖前写)
btn.onclick = function () { console.log("点击"); };

// 方式三:addEventListener(推荐,可绑多个,按顺序执行)
btn.addEventListener("click", function () {
console.log("点击 1");
});
btn.addEventListener("click", function () {
console.log("点击 2");
});

⚠️ onclick 方式后写会覆盖前一个;addEventListener 可同时绑定多个,是标准做法。

2. 解绑 removeEventListener

1
2
3
4
5
function handler() {
console.log("click");
}
btn.addEventListener("click", handler);
btn.removeEventListener("click", handler); // 解绑成功

⚠️ 关键坑:解绑必须传同一个函数引用。匿名函数无法解绑(addEventListener 里写 () => {} 就再也解不掉了)。需要解绑时,务必用命名函数。

二、事件类型

1. 鼠标事件

事件 触发时机 备注
click 单击 最常用
dblclick 双击
mouseenter / mouseleave 移入 / 移出 不冒泡,推荐
mouseover / mouseout 移入 / 移出 冒泡,会受子元素影响
mousemove 移动 配合坐标做拖拽
mousedown / mouseup 按下 / 松开
contextmenu 右键

2. 键盘事件

事件 触发时机
keydown 按下(推荐)
keyup 松开
keypress 字符键按下(已废弃,少用)

3. 表单事件

事件 触发时机
input 每次输入(实时)
change 值改变且失焦后
submit 表单提交
focus / blur 获得 / 失去焦点

4. 页面事件

事件 触发时机
load 页面全部加载完(含图片等资源)
DOMContentLoaded DOM 构建完(比 load 早)
scroll 滚动
resize 窗口大小改变

三、事件对象 event

事件回调的第一个参数就是事件对象 event(常简写 e):

1
2
3
4
5
6
btn.addEventListener("click", function (e) {
console.log(e.type); // "click" 事件类型
console.log(e.target); // 实际触发事件的元素
console.log(e.currentTarget); // 绑定监听器的元素(等同 this)
console.log(e.clientX, e.clientY); // 鼠标坐标
});

键盘事件的键信息:

1
2
3
4
input.addEventListener("keydown", function (e) {
console.log(e.key); // "a"、"Enter"(推荐,直观)
console.log(e.keyCode); // 65(数字,已废弃)
});

两个最重要的方法:

1
2
e.preventDefault();     // 阻止默认行为(链接跳转、表单提交、右键菜单)
e.stopPropagation(); // 阻止事件继续冒泡

四、事件流(捕获与冒泡)

一次完整的事件传播分三个阶段:捕获阶段 → 目标阶段 → 冒泡阶段。

1
2
3
4
5
// addEventListener 第三个参数控制在哪一阶段触发
// false(默认):冒泡阶段触发
// true:捕获阶段触发
parent.addEventListener("click", handler, false); // 冒泡
parent.addEventListener("click", handler, true); // 捕获
1
2
3
<div id="parent">
<button id="child">点我</button>
</div>
1
2
3
4
5
// 点击 child 时,打印顺序:
// 父捕获 → 子(目标)→ 父冒泡
parent.addEventListener("click", () => console.log("父-捕获"), true);
child.addEventListener("click", () => console.log("子-目标"));
parent.addEventListener("click", () => console.log("父-冒泡"), false);

⚠️ 记忆点:默认(false)是冒泡,事件从目标元素向上传到父元素,也是事件委托的基础。

阻止冒泡:e.stopPropagation() 让事件不再向上传播:

1
2
3
child.addEventListener("click", function (e) {
e.stopPropagation(); // 父元素的监听器不会触发了
});

区分:stopPropagation 阻止传播,preventDefault 阻止默认行为,两者互不影响。

五、事件委托(重点)

原理:利用事件冒泡,把监听器绑在父元素上,通过 event.target 判断实际点击的子元素。

1
2
3
4
5
<ul id="list">
<li>1</li>
<li>2</li>
<li>3</li>
</ul>
1
2
3
4
5
6
7
// 不用委托:给每个 li 都绑定(li 多时性能差,且新增 li 要重新绑)
// 用委托:只给父元素 ul 绑定一次
document.getElementById("list").addEventListener("click", function (e) {
if (e.target.tagName === "LI") { // 判断点的是不是 li
console.log(e.target.textContent); // 点哪个打印哪个
}
});

委托的两大好处:

  1. 性能:只绑一个监听器,替代绑 N 个。
  2. 动态元素友好:后加入的子元素自动生效,无需重新绑定。

⚠️ target vs currentTarget(委托里最易混):

  • e.target:实际点到的元素(点谁是谁)。
  • e.currentTarget:绑定监听器的元素(委托里是父元素 ul)。

事件委托判断目标时一定要用 e.target。

六、速查表

需求 写法
注册事件 el.addEventListener("click", fn)
解绑事件 el.removeEventListener("click", fn)(要同名函数)
阻止默认 e.preventDefault()
阻止冒泡 e.stopPropagation()
触发目标 e.target
绑定元素 e.currentTarget
按键名 e.key
鼠标坐标 e.clientX / e.clientY
捕获阶段 addEventListener("click", fn, true)
事件委托 父元素监听 + e.target 判断

七、复习要点

  1. 注册事件用 addEventListener(可绑多个),解绑要同名函数引用(匿名函数解不掉)。
  2. 常用事件:鼠标 click/mouseenter/mousemove、键盘 keydown、表单 input/change/submit、页面 load/DOMContentLoaded/scroll。
  3. e.target 是实际触发元素,e.currentTarget 是绑定元素;preventDefault 阻止默认,stopPropagation 阻止冒泡。
  4. 事件流三阶段:捕获 → 目标 → 冒泡,默认在冒泡阶段触发。
  5. 事件委托:监听父元素 + e.target 判断,省性能、支持动态元素。
  6. 忘了某个事件或属性,回本文对应小节查示例即可。

下一篇预告:BOM 与本地存储——window、location、history、navigator、localStorage/sessionStorage。