浏览器的事件模型是什么?事件委托的原理?
事件流三个阶段
1. 捕获阶段(Capture):从 window → 目标元素的祖先
2. 目标阶段(Target):到达目标元素
3. 冒泡阶段(Bubble):从目标元素 → window// 第三个参数:true = 捕获阶段监听,false/默认 = 冒泡阶段监听
element.addEventListener('click', handler, false) // 冒泡
element.addEventListener('click', handler, true) // 捕获
// stopPropagation:阻止事件传播
element.addEventListener('click', (e) => {
e.stopPropagation() // 阻止冒泡
})
// stopImmediatePropagation:阻止同元素上其他监听器执行
element.addEventListener('click', (e) => {
e.stopImmediatePropagation()
})事件委托
利用事件冒泡,在父元素上统一处理子元素的事件。
// ❌ 为每个 li 绑定事件
document.querySelectorAll('li').forEach(li => {
li.addEventListener('click', handleClick)
})
// ✅ 事件委托:在父元素上绑定一次
document.querySelector('ul').addEventListener('click', (e) => {
if (e.target.tagName === 'LI') {
console.log(e.target.textContent)
}
})事件委托优势
- 减少事件监听器数量,节省内存
- 动态元素自动处理:后添加的 li 也能响应点击
- 方便管理:统一在一个地方处理
事件委托 + data 属性
<div id="toolbar">
<button data-action="save">保存</button>
<button data-action="delete">删除</button>
<button data-action="cancel">取消</button>
</div>document.getElementById('toolbar').addEventListener('click', (e) => {
const action = e.target.closest('[data-action]')?.dataset.action
if (!action) return
switch (action) {
case 'save': saveData(); break
case 'delete': deleteData(); break
case 'cancel': cancelAction(); break
}
})closest 方法
closest() 从当前元素向上查找匹配的祖先(包括自身),非常适合事件委托:
// 点击按钮内的 span 时也能找到 button
button.addEventListener('click', (e) => {
const btn = e.target.closest('button')
if (btn) handleClick(btn)
})不适合事件委托的事件
focus/blur(不冒泡,可用focusin/focusout替代)mouseenter/mouseleave(不冒泡,可用mouseover/mouseout替代)load/unload/error
事件循环与事件
点击事件在宏任务中执行。如果事件处理函数中有异步操作,注意事件可能在异步操作完成前被再次触发。