Skip to content

事件处理

事件是用户与页面交互的基础。理解事件机制,才能构建响应式的 Web 应用。

🎯 事件基础#

什么是事件#

事件是浏览器或用户触发的特定行为,如点击、输入、滚动等。

// 用户点击按钮
button.onclick = function () {
console.log('按钮被点击')
}
// 用户输入文字
input.oninput = function () {
console.log('正在输入')
}
// 页面加载完成
window.onload = function () {
console.log('页面加载完成')
}

事件绑定方式#

HTML 属性#

<!-- 不推荐:HTML 和 JS 混合 -->
<button onclick="handleClick()">点击</button>
<script>
function handleClick() {
console.log('点击了')
}
</script>

DOM 属性#

const button = document.getElementById('btn')
// 绑定事件
button.onclick = function () {
console.log('点击了')
}
// 🔶 问题:只能绑定一个处理器
button.onclick = function () {
console.log('新的处理器')
}
// 之前的处理器被覆盖
// 移除事件
button.onclick = null

addEventListener(推荐)#

const button = document.getElementById('btn')
// 基本用法
button.addEventListener('click', function () {
console.log('处理器1')
})
button.addEventListener('click', function () {
console.log('处理器2')
})
// 两个处理器都会执行
// 带配置项
button.addEventListener(
'click',
function () {
console.log('点击')
},
{
capture: false, // 是否在捕获阶段触发
once: true, // 只执行一次
passive: true, // 不会调用 preventDefault
}
)
// 移除事件(需要相同的函数引用)
function handleClick() {
console.log('点击')
}
button.addEventListener('click', handleClick)
button.removeEventListener('click', handleClick)

事件对象#

基本属性#

element.addEventListener('click', function (event) {
// 事件类型
console.log(event.type) // "click"
// 触发事件的元素
console.log(event.target) // 实际点击的元素
// 绑定事件的元素
console.log(event.currentTarget) // 绑定处理器的元素
console.log(this) // 同上(非箭头函数)
// 时间戳
console.log(event.timeStamp)
// 是否可取消
console.log(event.cancelable)
})

鼠标事件属性#

element.addEventListener('click', function (event) {
// 鼠标位置(相对于视口)
console.log(event.clientX, event.clientY)
// 鼠标位置(相对于页面)
console.log(event.pageX, event.pageY)
// 鼠标位置(相对于屏幕)
console.log(event.screenX, event.screenY)
// 鼠标位置(相对于目标元素)
console.log(event.offsetX, event.offsetY)
// 鼠标按键
console.log(event.button) // 0左 1中 2右
console.log(event.buttons) // 位掩码
// ��饰键
console.log(event.ctrlKey)
console.log(event.shiftKey)
console.log(event.altKey)
console.log(event.metaKey) // Mac: Cmd, Win: Win键
})

键盘事件属性#

input.addEventListener('keydown', function (event) {
// 按键值
console.log(event.key) // "Enter", "a", "ArrowUp"
// 按键代码
console.log(event.code) // "Enter", "KeyA", "ArrowUp"
// 修饰键
console.log(event.ctrlKey)
console.log(event.shiftKey)
console.log(event.altKey)
console.log(event.metaKey)
// 是否重复(长按)
console.log(event.repeat)
// 常见判断
if (event.key === 'Enter') {
console.log('按下回车')
}
if (event.ctrlKey && event.key === 's') {
event.preventDefault()
console.log('Ctrl+S 保存')
}
})

事件流#

事件传播分为三个阶段:捕获、目标、冒泡。

捕获阶段#

// <div class="outer">
// <div class="inner">
// <button>点击</button>
// </div>
// </div>
const outer = document.querySelector('.outer')
const inner = document.querySelector('.inner')
const button = document.querySelector('button')
// 捕获阶段监听(从外到内)
outer.addEventListener('click', () => console.log('outer 捕获'), true)
inner.addEventListener('click', () => console.log('inner 捕获'), true)
button.addEventListener('click', () => console.log('button 捕获'), true)
// 点击 button 时输出顺序:
// outer 捕获
// inner 捕获
// button 捕获

冒泡阶段#

// 冒泡阶段监听(默认,从内到外)
outer.addEventListener('click', () => console.log('outer 冒泡'))
inner.addEventListener('click', () => console.log('inner 冒泡'))
button.addEventListener('click', () => console.log('button 冒泡'))
// 点击 button 时输出顺序:
// button 冒泡
// inner 冒泡
// outer 冒泡

完整事件流#

// 同时监听捕获和冒泡
outer.addEventListener('click', () => console.log('outer 捕获'), true)
outer.addEventListener('click', () => console.log('outer 冒泡'))
// 点击 button 时输出顺序:
// outer 捕获
// inner 捕获
// button(目标阶段,按注册顺序)
// inner 冒泡
// outer 冒泡

阻止事件#

preventDefault#

阻止默认行为:

// 阻止链接跳转
link.addEventListener('click', function (event) {
event.preventDefault()
console.log('不会跳转')
})
// 阻止表单提交
form.addEventListener('submit', function (event) {
event.preventDefault()
// 手动处理表单
})
// 阻止右键菜单
document.addEventListener('contextmenu', function (event) {
event.preventDefault()
// 显示自定义菜单
})
// 检查是否可以阻止
if (event.cancelable) {
event.preventDefault()
}

stopPropagation#

阻止事件传播:

inner.addEventListener('click', function (event) {
event.stopPropagation()
console.log('不会冒泡到 outer')
})
// 阻止同一元素的其他处理器
button.addEventListener('click', function (event) {
event.stopImmediatePropagation()
console.log('处理器1')
})
button.addEventListener('click', function (event) {
console.log('处理器2') // 不会执行
})

常用事件类型#

鼠标事件#

element.addEventListener('click', handler) // 点击
element.addEventListener('dblclick', handler) // 双击
element.addEventListener('mousedown', handler) // 按下
element.addEventListener('mouseup', handler) // 释放
element.addEventListener('mousemove', handler) // 移动
element.addEventListener('mouseenter', handler) // 进入(不冒泡)
element.addEventListener('mouseleave', handler) // 离开(不冒泡)
element.addEventListener('mouseover', handler) // 进入(冒泡)
element.addEventListener('mouseout', handler) // 离开(冒泡)
element.addEventListener('contextmenu', handler) // 右键
element.addEventListener('wheel', handler) // 滚轮

键盘事件#

element.addEventListener('keydown', handler) // 按下
element.addEventListener('keyup', handler) // 释放
element.addEventListener('keypress', handler) // 已废弃

表单事件#

input.addEventListener('input', handler) // 值改变(实时)
input.addEventListener('change', handler) // 值改变(失焦后)
input.addEventListener('focus', handler) // 获得焦点
input.addEventListener('blur', handler) // 失去焦点
form.addEventListener('submit', handler) // 提交
form.addEventListener('reset', handler) // 重置

页面事件#

window.addEventListener('load', handler) // 页面完全加载
window.addEventListener('DOMContentLoaded', handler) // DOM 加载完成
window.addEventListener('beforeunload', handler) // 页面即将卸载
window.addEventListener('unload', handler) // 页面卸载
window.addEventListener('resize', handler) // 窗口大小改变
window.addEventListener('scroll', handler) // 页面滚动

触摸事件#

element.addEventListener('touchstart', handler) // 触摸开始
element.addEventListener('touchmove', handler) // 触摸移动
element.addEventListener('touchend', handler) // 触摸结束
element.addEventListener('touchcancel', handler) // 触摸取消
// 触摸事件对象
element.addEventListener('touchstart', function (event) {
const touch = event.touches[0] // 第一个触摸点
console.log(touch.clientX, touch.clientY)
})

自定义事件#

CustomEvent#

// 创建自定义事件
const event = new CustomEvent('myEvent', {
detail: { message: '自定义数据' },
bubbles: true, // 是否冒泡
cancelable: true, // 是否可取消
})
// 监听自定义事件
element.addEventListener('myEvent', function (event) {
console.log(event.detail.message) // "自定义数据"
})
// 触发自定义事件
element.dispatchEvent(event)

实际应用#

// 简单的事件总线
class EventBus {
constructor() {
this.events = {}
}
on(event, callback) {
if (!this.events[event]) {
this.events[event] = []
}
this.events[event].push(callback)
}
off(event, callback) {
if (!this.events[event]) return
this.events[event] = this.events[event].filter((cb) => cb !== callback)
}
emit(event, data) {
if (!this.events[event]) return
this.events[event].forEach((callback) => callback(data))
}
}
const bus = new EventBus()
bus.on('userLogin', (user) => console.log('用户登录:', user))
bus.emit('userLogin', { name: '张三' })

事件处理最佳实践#

使用具名函数#

// 🔶 匿名函数无法移除
element.addEventListener('click', function () {})
// ✅ 具名函数可以移除
function handleClick() {}
element.addEventListener('click', handleClick)
element.removeEventListener('click', handleClick)

避免内存泄漏#

class Component {
constructor(element) {
this.element = element
this.handleClick = this.handleClick.bind(this)
this.element.addEventListener('click', this.handleClick)
}
handleClick() {
console.log('点击')
}
// 提供销毁方法
destroy() {
this.element.removeEventListener('click', this.handleClick)
this.element = null
}
}

使用 passive 优化滚动#

// 告诉浏览器不会调用 preventDefault
// 可以优化滚动性能
document.addEventListener('touchmove', handler, { passive: true })
// 如果需要阻止默认行为,不要用 passive
document.addEventListener(
'touchmove',
function (e) {
e.preventDefault()
},
{ passive: false }
)

总结#

绑定方式多处理器移除方式
HTML 属性设为空
DOM 属性设为 null
addEventListenerremoveEventListener
事件流阶段顺序capture 参数
捕获外→内true
目标--
冒泡内→外false(默认)

核心要点