事件是用户与页面交互的基础。理解事件机制,才能构建响应式的 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 = nulladdEventListener(推荐)#
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 })
// 如果需要阻止默认行为,不要用 passivedocument.addEventListener( 'touchmove', function (e) { e.preventDefault() }, { passive: false })总结#
| 绑定方式 | 多处理器 | 移除方式 |
|---|---|---|
| HTML 属性 | 否 | 设为空 |
| DOM 属性 | 否 | 设为 null |
| addEventListener | 是 | removeEventListener |
| 事件流阶段 | 顺序 | capture 参数 |
|---|---|---|
| 捕获 | 外→内 | true |
| 目标 | - | - |
| 冒泡 | 内→外 | false(默认) |
核心要点:
- 优先使用
addEventListener - 理解事件冒泡和捕获
- 用
preventDefault阻止默认行为 - 用
stopPropagation阻止传播 - 及时移除事件监听器避免内存泄漏