前端-JavaScript 事件大全 & 速查清单

发布于:2025-09-13 ⋅ 阅读:(18) ⋅ 点赞:(0)

前端-JavaScript 事件大全 & 速查清单

一文吃透 JS 事件:模型、阶段、注册方式、常见事件、事件对象、冒泡/捕获、阻止默认、事件委托、性能与最佳实践、以及自定义事件。


目录


事件模型与术语

  • 事件(Event):浏览器在某个时刻/行为发生时发出的“信号”,如 clickkeydownscroll
  • 事件目标(target):事件最初发生的那个元素。
  • 当前目标(currentTarget):当前触发回调的那个元素(总等于绑定监听器的元素)。
  • 传播(Propagation):事件沿 DOM 树传播的过程:捕获 → 目标 → 冒泡
  • 默认行为(Default Action):浏览器对某些事件的内置处理,比如 <a> 的跳转、表单提交、滚动等。

事件注册方式

1) HTML 内联(不推荐)

<button onclick="alert('hi')">Click</button>
  • 缺点:逻辑污染 HTML、只有冒泡阶段、难维护。

2) DOM Level 0(老方式,不冒泡参数不可选)

btn.onclick = function(e) { /* ... */ }
btn.onclick = null // 移除

3) DOM Level 2(推荐)

function handle(e){ /* ... */ }
btn.addEventListener('click', handle, /* options */)
btn.removeEventListener('click', handle)
  • 支持 捕获/冒泡once/passive/signal 等高级选项。

事件传播:捕获 → 目标 → 冒泡

!> 顺序:window → document → html → body → … → 目标(target) → … → body → html → document → window

parent.addEventListener('click', () => console.log('捕获 parent'), true)  // capture
child.addEventListener('click', () => console.log('目标 child'))          // at target
parent.addEventListener('click', () => console.log('冒泡 parent'))        // bubble
  • 第三个参数为 true 表示 捕获;默认是 冒泡
  • 大多数事件是冒泡的,极少数不冒泡(如 blur, focus, mouseenter, mouseleave)。

阻止默认与终止传播

el.addEventListener('click', (e) => {
  e.preventDefault()           // 阻止默认行为(如链接跳转、表单提交、滚动)
  e.stopPropagation()          // 阻止继续冒泡(或捕获)到更外层
  e.stopImmediatePropagation() // 还会阻止当前元素上后续监听器
}, { passive: false })
  • passive: true 时对“可滚动”类事件(touchstart/move, wheel无法 preventDefault()

事件对象 Event 常用属性

属性 含义 备注
type 事件类型 'click'
target 事件最初触发元素 可能是深层子节点
currentTarget 正在处理监听器的元素 永远等于绑定元素
eventPhase 1 捕获 / 2 目标 / 3 冒泡 调试传播阶段
timeStamp 事件时间戳 单位毫秒
defaultPrevented 是否已调用 preventDefault() 布尔
composedPath() 真实传播路径 含 Shadow DOM
isTrusted 用户触发还是脚本触发 只读

鼠标/指针相关

clientX/Y, pageX/Y, screenX/Y, button(0左1中2右), buttons, altKey/ctrlKey/shiftKey/metaKey

键盘相关

key(如 'a', 'Enter'), code(物理键位,如 KeyA), repeat


常见事件类型速查

鼠标类

click, dblclick, mousedown, mouseup, mousemove, contextmenu, mouseenter/leave不冒泡), mouseover/out冒泡)。

指针类(推荐统一输入)

pointerdown/up/move/cancel/enter/leave/over/out, gotpointercapture/lostpointercapture

触摸类(移动端旧接口)

touchstart/move/end/cancel(更建议用 Pointer Events 统一处理)。

键盘类

keydown, keyup(无 keypress 了)。

表单类

input, change, focus/blur(不冒泡,使用 focusin/focusout 代替可冒泡版)。

其他

submit, reset, scroll, wheel, resize, DOMContentLoaded, load, beforeunload, visibilitychange 等。

📌 常见事件类型

类别 事件
鼠标 click, dblclick, mousedown, mouseup, mousemove, mouseenter, mouseleave
键盘 keydown, keyup, keypress (废弃)
表单 input, change, submit, focus, blur
触摸 touchstart, touchmove, touchend, touchcancel
指针 pointerdown, pointerup, pointermove, pointercancel
页面 load, unload, resize, scroll, visibilitychange
剪贴板 copy, cut, paste
拖放 dragstart, dragover, drop, dragend
自定义事件 new CustomEvent(type, options) + dispatchEvent

事件委托(高性能必备)

把子元素的事件监听“交给”父元素统一处理,减少监听器数量。

<ul id="list">
  <li data-id="1">One</li>
  <li data-id="2">Two</li>
</ul>
<script>
const list = document.getElementById('list')
list.addEventListener('click', (e) => {
  const li = e.target.closest('li')  // 命中近邻 li
  if (!li || !list.contains(li)) return
  console.log('点击了 li#', li.dataset.id)
})
</script>
  • 利用 冒泡 + closest() 命中实际目标。
  • 动态新增的子节点也天然生效,适合长列表。

addEventListener 选项

el.addEventListener('touchmove', onMove, {
  capture: false,     // 是否在捕获阶段触发
  once: true,         // 回调执行一次后自动移除
  passive: true,      // 告诉浏览器“不会调用 preventDefault” → 滚动更流畅
  signal: abortCtrl.signal // 可用 AbortController 统一移除
})
// 统一移除
abortCtrl.abort()

自定义事件 CustomEvent

const evt = new CustomEvent('cart:add', {
  detail: { id: 123, qty: 2 },
  bubbles: true,
  composed: true // 允许穿越 Shadow DOM 边界
})
document.querySelector('#add').dispatchEvent(evt)

document.addEventListener('cart:add', (e) => {
  console.log('添加到购物车:', e.detail)
})
  • 业务中可用命名空间风格:'cart:add', 'user:login' 等。

指针事件 vs 鼠标/触摸事件

  • Pointer Events 统一处理鼠标、触摸、手写笔:更现代,建议优先。
  • 支持压力/倾角等扩展属性(pressure, tiltX/Y, pointerType)。
  • 旧代码需要兼容时:同时监听鼠标/触摸或做特性检测。

键盘与输入法细节

input.addEventListener('keydown', (e) => {
  if (e.key === 'Enter' && !e.isComposing) { /* 提交 */ }
})
// 输入法(中文拼音/日文等)
input.addEventListener('compositionstart', () => {})
input.addEventListener('compositionend', () => {})
  • 输入法合成期keydown 可能多次触发但不应提交,需判定 isComposing

表单事件小贴士

  • input:每次内容变动就触发;change:失焦或回车才触发(取决于控件)。
  • focus/blur 不冒泡,若需要委托用 focusin/focusout
  • 阻止表单默认提交:form.addEventListener('submit', e => e.preventDefault())

移除事件监听的坑

  • removeEventListener 必须传入与 addEventListener 同一个函数引用同一组 options(capture 值)
el.addEventListener('click', () => {})       // ❌ 匿名函数无法移除
const fn = () => {}
el.addEventListener('click', fn, true)
el.removeEventListener('click', fn, true)    // ✅ capture 必须一致

Shadow DOM 与 composedPath

el.addEventListener('click', (e) => {
  const path = e.composedPath()  // 最真实的传播路径(含 Shadow 边界)
  console.log(path.map(n => n.nodeName).join(' → '))
})
  • 在 Web Components 中判断命中元素要优先考虑 composedPath()

性能优化与最佳实践清单

  • 事件委托:长列表/频繁增删节点时优先。
  • passivewheel/touchstart/touchmove 等尽量 passive: true
  • 节流/防抖scroll/resize/mousemove 高频事件配合 requestAnimationFrame 或节流。
  • 一次性监听:能 onceonce,避免遗留。
  • 统一移除AbortController 批量注销。
  • 减少匿名函数,便于移除与复用。
  • 避免阻塞主线程:重活放到 Web Worker 或分片执行。

FAQ:常见疑惑

Q1:targetcurrentTarget 有啥区别?

  • target 是“谁被点了”;currentTarget 是“谁在处理监听”。委托里两者常不同。

Q2:为什么 preventDefault 没效果?

  • 你可能用了 passive: true(不可阻止),或事件本身无默认行为。

Q3:stopPropagationstopImmediatePropagation 区别?

  • 前者阻止继续传播到父级;后者还会阻止同一元素后续监听器。

Q4:哪些事件不冒泡?

  • 常见如 blur, focus, mouseenter, mouseleave。用可冒泡替代:focusin/focusoutmouseover/mouseout

示例汇总

节流滚动 + passive

let ticking = false
window.addEventListener('scroll', (e) => {
  if (!ticking) {
    requestAnimationFrame(() => {
      // 处理滚动逻辑...
      ticking = false
    })
    ticking = true
  }
}, { passive: true })

统一移除(AbortController)

const ac = new AbortController()
const opts = { signal: ac.signal }

window.addEventListener('resize', onResize, opts)
document.addEventListener('visibilitychange', onVis, opts)
// ...更多监听
ac.abort() // 一键全部移除

组合:委托 + closest + 自定义事件

const ac = new AbortController()
document.body.addEventListener('click', (e) => {
  const btn = e.target.closest('[data-add]')
  if (!btn) return
  btn.dispatchEvent(new CustomEvent('cart:add', {
    detail: { id: btn.dataset.add, qty: 1 }, bubbles: true
  }))
}, { signal: ac.signal })

document.addEventListener('cart:add', (e) => {
  console.log('添加:', e.detail)
})

小结:事件不是“点一下那么简单”。理解 传播阶段、善用 委托、配合 passive/once/signal,加点 CustomEvent 的业务语义,你的交互会更丝滑,代码也更优雅。继续冲~🚀