JavaScript 中的事件,可以理解成浏览器或用户发出的“信号”。当页面上的某个事情发生时(比如点击按钮、按下键盘、页面加载完成),浏览器就会产生一个对应的事件,我们可以通过编写代码来“监听”并响应这些信号,让网页变得可交互。

1. 事件流:捕获与冒泡

当一个元素上的事件被触发时,事件并不会只在该元素上处理,而是沿着 DOM 树传播。这个过程分为三个阶段:
1、捕获阶段:事件从最外层的 window 开始,逐层向下传递,直到到达触发事件的元素。
2、目标阶段:事件到达实际触发的那个元素。
3、冒泡阶段:事件从触发元素开始,逐层向上冒泡,直到 window。

默认情况下,事件处理程序会在冒泡阶段执行,但我们可以选择在捕获阶段就处理它。

<div id="outer">
  <div id="inner">点击这里</div>
</div>
const outer = document.getElementById('outer');
const inner = document.getElementById('inner');

// 冒泡阶段(默认)
outer.addEventListener('click', () => console.log('外层冒泡'), false);
inner.addEventListener('click', () => console.log('内层冒泡'), false);

// 捕获阶段
outer.addEventListener('click', () => console.log('外层捕获'), true);
inner.addEventListener('click', () => console.log('内层捕获'), true);

// 点击 inner 后输出顺序:
// 外层捕获 → 内层捕获 → 内层冒泡 → 外层冒泡

2. 绑定事件处理程序

有三种方式为元素绑定事件:
① HTML 行内属性

<button onclick="handleClick()">按钮</button>

缺点:将结构与行为耦合,不利于维护,且作用域链特殊,不推荐使用。
② DOM 属性赋值

const btn = document.querySelector('button');
btn.onclick = function() {
  console.log('点击');
};
// 取消绑定
btn.onclick = null;

缺点:同一事件只能绑定一个处理函数,后面的会覆盖前面的。
③ addEventListener (推荐)

btn.addEventListener('click', function(e) {
  console.log('第一次绑定');
});
btn.addEventListener('click', function(e) {
  console.log('第二次绑定');
});
// 两个都会执行,不会互相覆盖

可以绑定多个处理函数。
可通过第三个参数控制是在捕获阶段还是冒泡阶段执行:
true / { capture: true }:捕获阶段
false / { capture: false }:冒泡阶段(默认)
可以通过 removeEventListener 解绑,但必须传入同一个函数引用。

现代开发中,第三个参数更多使用选项对象:

btn.addEventListener('click', function handler() {console.log('只绑定一次,再捕获阶段触发');},{
  once:true,    // 只执行一次,自动解绑
  passive: true, // 不会调用 preventDefault(),提升滚动性能
  capture: false // 是否在捕获阶段触发
});

3. 事件对象

事件处理程序会收到一个自动生成的 event 对象,包含了与该事件相关的所有信息。

element.addEventListener('click', function(event) {
  // 常用属性
  event.type          // 事件类型,如 'click'
  event.target        // 实际触发事件的元素(最内层)
  event.currentTarget // 绑定事件监听器的元素(即 this)
  event.clientX       // 鼠标相对于视口的 X 坐标
  event.clientY       // 鼠标相对于视口的 Y 坐标
  event.key           // 键盘事件中按下的键值
  event.keyCode       // 已废弃,请使用 key

  // 常用方法
  event.preventDefault()   // 阻止默认行为(如链接跳转、表单提交)
  event.stopPropagation()  // 阻止事件继续冒泡或捕获
  event.stopImmediatePropagation() // 阻止冒泡,并阻止同一元素上的其他监听器执行
});

target 和 currentTarget 的区别很重
target:你真正点击的那个元素(源头)。
currentTarget:绑定了当前处理函数的那个元素(监听器所在)。

4. 事件委托(Event Delegation)

如果有很多子元素需要绑定同一个事件(比如列表项),可以利用冒泡机制,将监听器加在它们的共同父元素上,然后通过 event.target 判断具体是哪个子元素。
优点:
减少内存:只需一个监听器,而不是给每个子元素都绑定。
支持动态元素:后续通过 JS 添加的子元素也能自动拥有该行为。

const list = document.getElementById('list');

list.addEventListener('click', function(e) {
  // 判断点击的是否是 li
  if (e.target.tagName === 'LI') {
    console.log('你点击了:', e.target.textContent);
  }
});

使用 matches 方法更灵活:

list.addEventListener('click', function(e) {
  if (e.target.matches('.item-class')) {
    // 处理
  }
});

5. 常见事件类型速览

鼠标事件

click、dblclick
mousedown、mouseup
mousemove
mouseenter、mouseleave(不冒泡)
mouseover、mouseout(会冒泡)
contextmenu(右键菜单)

键盘事件

keydown、keyup(推荐用来检测按键)
keypress(已废弃,不推荐)

表单事件

submit:表单提交时触发,常用于验证。
change:输入框内容改变且失去焦点时,或下拉选项改变时触发。
input:输入框内容实时改变时触发(更适合实时监听)。
focus、blur:元素获得/失去焦点(不冒泡)。
focusin、focusout:获得/失去焦点(会冒泡,可用于委托)。

文档/窗口事件

DOMContentLoaded:HTML 解析完毕,无需等待样式表、图片加载完。
load:页面所有资源(含图片)加载完成。
resize:窗口大小改变。
scroll:滚动事件(可配合 passive 优化性能)。
beforeunload:页面即将卸载,可用来提示用户保存。

触摸事件(移动端)

touchstart、touchmove、touchend

其他

copy、cut、paste:剪贴板事件。
dragstart、dragover、drop 等:拖拽事件。

6. 自定义事件

除了浏览器原生事件,我们还可以自己创建并触发事件:

// 创建一个事件
const myEvent = new CustomEvent('myAction', {
  detail: { message: '自定义事件触发了' }
});

// 监听它
document.addEventListener('myAction', function(e) {
  console.log(e.detail.message);
});

// 触发
document.dispatchEvent(myEvent);

这在组件间通信或复杂交互中非常实用。

7. 几个重要技巧与注意事项

1、this 的指向:在通过 addEventListener 绑定的普通函数中,this 指向 event.currentTarget(即绑定事件的元素);如果使用箭头函数,this 会保持外层作用域的值。

2、性能优化:对于 scroll、resize、mousemove 等高频事件,应结合节流(throttle) 或防抖(debounce) 处理。

3、passive 选项:在滚动监听中设置 { passive: true } 可以告诉浏览器你不会调用 preventDefault(),从而让滚动更丝滑。

4、解绑事件:记得在适当的时机用 removeEventListener 清理,避免内存泄漏。

Logo

有“AI”的1024 = 2048,欢迎大家加入2048 AI社区

更多推荐