讲解

事件是浏览器通知 JavaScript「有事发生」的机制:点击、输入、滚动、键盘、加载完成都是事件。监听事件的现代方式是 addEventListener:el.addEventListener('click', handler),同一个元素可以挂多个监听器,对应的移除方法是 removeEventListener(要传入同一个函数引用才能移除成功,匿名函数摘不掉)。回调收到一个事件对象 event,常用属性有 event.target(实际触发的元素)、event.currentTarget(监听挂着的元素)、event.key(键盘事件的键名)。

事件流是理解复杂交互的关键:事件先从 document 向下「捕获」到目标元素,再向上「冒泡」回 document。addEventListener 默认监听冒泡阶段(第三个参数传 true 监听捕获阶段)。冒泡机制支撑了「事件委托」模式:不给一百个 li 各挂监听,而在父 ul 上挂一个,通过 event.target 判断点的是谁——动态新增的 li 也自动生效,性能和可维护性双赢。

两个控制方法:event.preventDefault() 阻止默认行为(表单提交跳转、链接跳转、右键菜单);event.stopPropagation() 阻止事件继续冒泡(慎用,会干扰上层正常监听,排查「点了没反应」时先怀疑有人调了它)。常用事件清单:click、input(输入实时触发)、change(失焦后变更)、submit、keydown、scroll、resize、DOMContentLoaded(DOM 就绪)。

示例

<ul id="list">
  <li><button data-action="done">完成</button> 事项一</li>
  <li><button data-action="done">完成</button> 事项二</li>
</ul>
<form id="form"><input name="q" /><button>搜索</button></form>
// 基础监听
const list = document.querySelector('#list');

// 事件委托:一个监听器服务所有按钮,包括未来新增的
list.addEventListener('click', (event) => {
  const btn = event.target.closest('button[data-action]');
  if (!btn) return; // 点的不是目标按钮
  console.log('点击了', btn.dataset.action, ',所在行:', btn.closest('li').textContent);
});

// 表单:阻止默认提交跳转
document.querySelector('#form').addEventListener('submit', (event) => {
  event.preventDefault();
  const q = new FormData(event.target).get('q');
  console.log('搜索词:', q);
});

// 键盘事件
document.addEventListener('keydown', (event) => {
  if (event.key === 'Enter' && (event.ctrlKey || event.metaKey)) {
    console.log('按下了 Ctrl/Cmd + Enter');
  }
});

// 可移除的监听:必须保留函数引用
function once() {
  console.log('只响应一次');
  document.removeEventListener('click', once);
}
document.addEventListener('click', once);

常见坑

  • removeEventListener 摘不掉匿名函数:addEventListener('click', (e) => ...) 传的匿名函数没有引用可摘。需要移除时把函数存成变量。
  • 分不清 target 和 currentTarget:冒泡场景下 target 是实际被点的深层元素(可能是按钮里的 span),currentTarget 才是挂监听的那个。委托里用 closest 从 target 向上找最稳。
  • 滥用 stopPropagation:阻断了冒泡,上层的委托监听、全局点击关闭弹层等逻辑全部失效。能靠判断解决就不要阻断。
  • 给大量子元素逐个挂监听:几百个监听既费内存又难维护,事件委托一个搞定,还自动覆盖动态元素。

小结

addEventListener 挂监听,事件沿「捕获→目标→冒泡」传播;委托模式利用冒泡在父级统一处理;preventDefault 拦默认行为。下一章:定时器。