网站首页 > 技术文章 正文
JavaScript 事件是交互开发的核心,它允许代码响应用户操作、浏览器行为或文档变化。本文将从基础概念出发,分类梳理常用事件,并结合实战场景说明其应用,帮助你系统掌握事件编程。
一、事件基础:核心概念与绑定方式
在学习具体事件前,需先理解事件的核心逻辑 ——“谁在什么情况下执行什么操作”。
1. 事件三要素
任何事件都包含三个核心部分,缺一不可:
- 事件源:触发事件的元素(如按钮、输入框、窗口)。
- 事件类型:事件的具体类别(如 “点击”“键盘按下”“页面加载”)。
- 事件处理程序:事件触发时执行的函数(可通过匿名函数、命名函数定义)。
2. 事件流(Event Flow)
当事件触发时,浏览器会按特定顺序传播事件,即事件流,分为三个阶段(DOM2 级规范):
- 捕获阶段:事件从最顶层的document向下传播到事件源的父元素。
- 目标阶段:事件到达事件源本身。
- 冒泡阶段:事件从事件源向上传播回document(大部分日常开发用冒泡阶段)。
示例:点击按钮时,事件流顺序为 document → html → body → 按钮父元素 → 按钮(目标)→ 按钮父元素 → body → html → document。
3. 事件绑定方式
常用的事件绑定方式有三种,各有优缺点:
绑定方式 | 语法示例 | 特点 |
HTML 内联绑定 | <button onclick="handleClick()"> | 耦合 HTML 与 JS,维护性差,不推荐 |
DOM0 级绑定 | btn.onclick = handleClick | 简单直观,仅支持冒泡,同一事件只能绑定一个函数 |
DOM2 级绑定 | btn.addEventListener('click', handleClick) | 支持捕获 / 冒泡,可绑定多个函数,推荐使用 |
解绑注意:DOM0 级用 btn.onclick = null,DOM2 级,需用 removeEventListener(且函数必须是命名函数,不能是匿名函数)。
二、常用事件分类与应用场景
按触发场景将事件分为 6 大类,每类包含核心事件、触发时机及简单示例。
1. 鼠标事件(Mouse Events)
最常用的交互事件,响应鼠标操作(点击、移动、滚轮等)。
事件名称 | 触发时机 | 示例代码 |
click | 鼠标左键单击(松开时触发) | btn.addEventListener('click', () => alert('按钮被点击')) |
dblclick | 鼠标左键双击 | div.addEventListener('dblclick', () => div.style.color = 'red') |
mousedown | 鼠标任意键按下(不区分左右键) | box.addEventListener('mousedown', () => box.style.border = '2px solid #000') |
mouseup | 鼠标任意键松开 | box.addEventListener('mouseup', () => box.style.border = 'none') |
mousemove | 鼠标在元素内移动(持续触发) | document.addEventListener('mousemove', (e) => console.log(坐标: \({e.clientX},\){e.clientY})) |
mouseover | 鼠标移入元素或其子元素(会冒泡) | nav.addEventListener('mouseover', (e) => e.target.style.backgroundColor = '#f5f5f5') |
mouseout | 鼠标移出元素或其子元素(会冒泡) | nav.addEventListener('mouseout', (e) => e.target.style.backgroundColor = 'transparent') |
mouseenter | 鼠标仅移入元素本身(不冒泡,性能更优) | card.addEventListener('mouseenter', () => card.style.transform = 'scale(1.05)') |
mouseleave | 鼠标仅移出元素本身(不冒泡) | card.addEventListener('mouseleave', () => card.style.transform = 'scale(1)') |
contextmenu | 鼠标右键单击(触发上下文菜单前) | document.addEventListener('contextmenu', (e) => { e.preventDefault(); alert('禁用右键菜单') }) |
wheel | 鼠标滚轮滚动(可获取滚动方向) | document.addEventListener('wheel', (e) => console.log('滚动方向:', e.deltaY > 0 ? '向下' : '向上')) |
关键区别:mouseover/mouseout 会因子元素触发冒泡,而 mouseenter/mouseleave 仅作用于元素本身,适合卡片、菜单等组件。
2. 键盘事件(Keyboard Events)
响应键盘操作,常用于快捷键、表单输入控制等场景。
事件名称 | 触发时机 | 示例代码 |
keydown | 键盘任意键按下(持续触发,支持所有键) | input.addEventListener('keydown', (e) => { if (e.key === 'Enter') submitForm() }) |
keyup | 键盘任意键松开(仅触发一次) | document.addEventListener('keyup', (e) => console.log('松开的键:', e.key)) |
keypress | 键盘字符键按下(仅支持可打印字符,逐步淘汰) | (建议用 keydown 替代,兼容性更好) |
常用属性:e.key(语义化键名,如 'Enter'/'Escape')、e.keyCode(键码,如 Enter 是 13,逐步淘汰)、e.ctrlKey/e.shiftKey(是否按住 Ctrl/Shift 键)。
3. 表单事件(Form Events)
专门用于表单元素(
input/select/textarea/form),处理输入、提交等行为。
事件名称 | 触发时机 | 示例代码 |
input | 表单元素值实时变化(输入、粘贴、删除等) |
|
change | 表单元素值确认变化(失去焦点或选择后) | select.addEventListener('change', (e) => console.log('选中的值:', e.target.value)) |
blur | 元素失去焦点(点击外部或按 Tab 键) |
|
focus | 元素获得焦点(点击或按 Tab 键) | usernameInput.addEventListener('focus', () => usernameInput.style.border = '2px solid #4CAF50') |
submit | 表单提交(点击提交按钮或按 Enter) | form.addEventListener('submit', (e) => { e.preventDefault(); // 阻止默认提交,执行自定义验证 }) |
reset | 表单重置(点击重置按钮) | form.addEventListener('reset', () => alert('表单已重置')) |
实战重点:input 用于实时验证,submit 必须阻止默认行为(e.preventDefault())后再执行自定义逻辑,避免页面刷新。
4. 文档 / 窗口事件(Document/Window Events)
响应文档加载、窗口大小变化、滚动等浏览器级行为。
事件名称 | 触发时机 | 示例代码 |
DOMContentLoaded | DOM 树加载完成(无需等待图片、CSS 等资源) | document.addEventListener('DOMContentLoaded', () => { console.log('DOM已就绪,可以操作元素') }) |
load | 整个页面(DOM + 图片 + CSS 等)加载完成 | window.addEventListener('load', () => { console.log('页面所有资源已加载') }) |
resize | 窗口大小改变时(持续触发) | window.addEventListener('resize', () => { console.log('窗口宽度:', window.innerWidth) }) |
scroll | 页面或元素滚动时(持续触发) | window.addEventListener('scroll', () => { if (window.scrollY > 500) showBackToTopBtn() }) |
beforeunload | 页面关闭 / 刷新前(用于提示用户保存) | window.addEventListener('beforeunload', (e) => { e.returnValue = '确定离开吗?未保存的内容将丢失'; return '确定离开吗?' }) |
unload | 页面关闭时(仅用于简单清理,不推荐复杂操作) | window.addEventListener('unload', () => { // 发送统计数据(可能丢失,建议用beacon API) }) |
关键区别:DOMContentLoaded 比 load 触发更早,是操作 DOM 的最佳时机;scroll/resize 持续触发时需加节流优化(如每 100ms 执行一次),避免性能问题。
5. 触摸事件(Touch Events)
针对移动端设备,响应手指触摸操作(替代鼠标事件,避免延迟)。
事件名称 | 触发时机 | 示例代码 |
touchstart | 手指触摸屏幕时 | slide.addEventListener('touchstart', (e) => { startX = e.touches[0].clientX; }) |
touchmove | 手指在屏幕上滑动时(持续触发) | slide.addEventListener('touchmove', (e) => { moveX = e.touches[0].clientX; if (moveX - startX > 50) nextSlide(); }) |
touchend | 手指离开屏幕时 | slide.addEventListener('touchend', () => { startX = 0; moveX = 0; }) |
touchcancel | 触摸被中断(如来电、弹窗) | slide.addEventListener('touchcancel', () => { // 重置触摸状态 }) |
注意:触摸事件会触发鼠标事件(如 touchstart 后触发 mousedown),需用 e.preventDefault() 阻止,或在 addEventListener 中加 passive: true 优化滚动性能(如 window.addEventListener('touchmove', handleMove, { passive: true }))。
6. UI 事件(UI Events)
响应元素 UI 状态变化,如元素显示 / 隐藏、焦点变化等(部分与其他分类重叠,重点列常用)。
事件名称 | 触发时机 | 示例代码 |
visibilitychange | 页面可见性变化(切换标签、最小化) | document.addEventListener('visibilitychange', () => { if (document.hidden) pauseVideo(); else playVideo(); }) |
focusin | 元素或其子元素获得焦点(冒泡,替代focus) | form.addEventListener('focusin', (e) => e.target.style.border = '2px solid #4CAF50') |
focusout | 元素或其子元素失去焦点(冒泡,替代blur) | form.addEventListener('focusout', (e) => e.target.style.border = '1px solid #ccc') |
三、实战应用:从基础到进阶
掌握事件的核心是 “灵活运用”,以下是 3 个高频实战场景,覆盖事件委托、事件对象、多事件协作。
1. 场景 1:事件委托(优化动态列表)
问题:动态添加的列表项(如购物车商品)无法绑定事件,直接给每个项绑定会浪费性能。
解决方案:利用事件冒泡,将事件绑定到父元素(如列表容器),通过 e.target 定位触发元素。
<ul id="todoList">
<li>学习事件委托</li>
<li>完成实战示例</li>
</ul>
<button id="addTodo">添加任务</button>
<script>
const todoList = document.getElementById('todoList');
const addTodo = document.getElementById('addTodo');
// 1. 委托事件到父元素
todoList.addEventListener('click', (e) => {
// 确认触发元素是列表项(避免点击空白处触发)
if (e.target.tagName === 'LI') {
e.target.style.textDecoration = 'line-through'; // 标记完成
}
});
// 2. 动态添加列表项
addTodo.addEventListener('click', () => {
const li = document.createElement('li');
li.textContent = '新任务:' + new Date().getSeconds();
todoList.appendChild(li);
});
</script>
优势:只需绑定 1 次事件,支持任意动态元素,减少内存占用。
2. 场景 2:表单验证(多事件协作)
需求:实时验证用户名(长度≥3)、密码(含数字),提交时阻止无效表单。
<form id="loginForm">
<div>
<label>用户名:</label>
<input type="text" id="username" name="username">
<span class="error" id="usernameError"></span>
</div>
<div>
<label>密码:</label>
<input type="password" id="password" name="password">
<span class="error" id="passwordError"></span>
</div>
<button type="submit">登录</button>
</form>
<script>
const form = document.getElementById('loginForm');
const username = document.getElementById('username');
const password = document.getElementById('password');
const usernameError = document.getElementById('usernameError');
const passwordError = document.getElementById('passwordError');
// 1. 实时验证用户名(input事件)
username.addEventListener('input', (e) => {
const value = e.target.value.trim();
if (value.length < 3) {
usernameError.textContent = '用户名至少3个字符';
usernameError.style.color = 'red';
} else {
usernameError.textContent = '';
}
});
// 2. 实时验证密码(blur事件,失去焦点时验证)
password.addEventListener('blur', (e) => {
const value = e.target.value;
if (!/\d/.test(value)) { // 正则判断是否含数字
passwordError.textContent = '密码必须包含数字';
passwordError.style.color = 'red';
} else {
passwordError.textContent = '';
}
});
// 3. 提交表单(submit事件,阻止默认行为)
form.addEventListener('submit', (e) => {
e.preventDefault(); // 阻止页面刷新
// 二次验证(避免用户跳过实时验证直接提交)
const isUsernameValid = username.value.trim().length >= 3;
const isPasswordValid = /\d/.test(password.value);
if (isUsernameValid && isPasswordValid) {
alert('表单验证通过,正在提交...');
// 这里执行AJAX提交逻辑
} else {
alert('请修正表单错误后重试');
}
});
</script>
3. 场景 3:拖拽功能(鼠标事件协作)
需求:实现一个可拖拽的 div,支持鼠标按下拖动、松开停止。
<div id="dragBox" style="width: 100px; height: 100px; background: #4CAF50; position: absolute; cursor: move;"></div>
<script>
const dragBox = document.getElementById('dragBox');
let isDragging = false; // 标记是否正在拖拽
let startX, startY, offsetX, offsetY; // 初始位置和偏移量
// 1. 鼠标按下:记录初始位置
dragBox.addEventListener('mousedown', (e) => {
isDragging = true;
// 获取鼠标相对于盒子的偏移量(避免鼠标在盒子内不同位置导致跳动)
startX = e.clientX;
startY = e.clientY;
offsetX = startX - dragBox.offsetLeft;
offsetY = startY - dragBox.offsetTop;
dragBox.style.opacity = '0.8'; // 拖拽时半透明
});
// 2. 鼠标移动:更新盒子位置(绑定到document,避免鼠标移出盒子后停止)
document.addEventListener('mousemove', (e) => {
if (!isDragging) return; // 未拖拽时不执行
// 计算盒子新位置
const newLeft = e.clientX - offsetX;
const newTop = e.clientY - offsetY;
// 限制盒子在窗口内(可选)
const maxLeft = window.innerWidth - dragBox.offsetWidth;
const maxTop = window.innerHeight - dragBox.offsetHeight;
const finalLeft = Math.max(0, Math.min(newLeft, maxLeft));
const finalTop = Math.max(0, Math.min(newTop, maxTop));
// 更新位置
dragBox.style.left = finalLeft + 'px';
dragBox.style.top = finalTop + 'px';
});
// 3. 鼠标松开:停止拖拽
document.addEventListener('mouseup', () => {
if (isDragging) {
isDragging = false;
dragBox.style.opacity = '1'; // 恢复不透明
}
});
</script>
四、注意事项与性能优化
- 阻止不必要的冒泡 / 默认行为:冒泡:用 e.stopPropagation() 或 e.stopImmediatePropagation()(阻止后续处理函数)。默认行为:用 e.preventDefault()(如阻止表单提交、链接跳转),但注意不要滥用(如阻止页面滚动)。
- 优化高频触发事件:
scroll/resize/mousemove/touchmove 会持续触发,需用节流(throttle) 或防抖(debounce) 控制执行频率:
// 节流示例:每100ms执行一次
function throttle(fn, delay = 100) {
let lastTime = 0;
return (...args) => {
const now = Date.now();
if (now - lastTime >= delay) {
fn.apply(this, args);
lastTime = now;
}
};
}
// 使用
window.addEventListener('scroll', throttle(() => {
console.log('滚动位置:', window.scrollY);
}));
- 及时解绑事件:
单页应用(SPA)或动态元素删除时,需解绑事件避免内存泄漏:
const handleClick = () => alert('点击');
btn.addEventListener('click', handleClick);
// 解绑(必须用同一命名函数)
btn.removeEventListener('click', handleClick);
- 移动端优先考虑触摸事件:
鼠标事件在移动端有 300ms 延迟(历史兼容问题),推荐用 touchstart/touchmove/touchend,或在 HTML 头部加 <meta name="viewport" content="width=device-width"> 消除延迟。
总结
JavaScript 事件是交互开发的基石,掌握 “事件分类→事件对象→事件流→实战场景” 的逻辑链,能让你灵活应对各类需求。重点记住:
- 优先用 addEventListener 绑定事件,支持多处理函数和捕获 / 冒泡控制;
- 动态元素用事件委托优化;
- 高频事件用节流 / 防抖提升性能;
- 移动端优先用触摸事件,避免延迟。
结合本文的事件列表和实战示例,可快速定位并解决开发中的交互问题。
- 上一篇: 快看!百度提前批的面试难度,你能拿下吗?
- 下一篇: 《前端研发规范》+配置文件_前端开发规范
猜你喜欢
- 2024-12-25 css兼容性问题及一些常见问题汇总大全,赶快收藏!
- 2024-12-25 指甲变黑了是身体出了问题吗,黑指甲是否有癌变的可能?
- 2024-12-25 如何学习网络前端开发 网络前端培训
- 2024-12-25 基于vue实现可视化拖拽编辑器,页面生成工具,提升前端开发效率
- 2024-12-25 完美收藏 | 高校新媒体运营最实用工具大全!
- 2024-12-25 潮流的博美宠物狗名字大全走在时尚前端-可爱点
- 2024-12-25 前端开发效率工具推荐(二) 前端最好用的开发工具
- 2024-12-25 2016浏览器内核css前缀大全 ie浏览器css前缀
- 2024-12-25 电子围栏系统常见故障解决方法 电子围栏系统常见故障解决方法有哪些
- 2024-12-25 vue3 学习笔记(十)——插槽使用大全
你 发表评论:
欢迎- 09-28前端同学狂喜_前端gui
- 09-28默认选择 React,等于是在扼杀前端创新
- 09-28《前端研发规范》+配置文件_前端开发规范
- 09-28JavaScript 常用事件大全:从基础到实战应用
- 09-28快看!百度提前批的面试难度,你能拿下吗?
- 09-28找工作速看!今日最新热招岗位全曝光
- 09-28Vue3+ElementPlus 免费后台管理模板VueNextAdmin
- 09-28前端开发的物理外挂来了,爽到飞起!
- 最近发表
- 标签列表
-
- 前端设计模式 (75)
- 前端性能优化 (51)
- 前端模板 (66)
- 前端跨域 (52)
- 前端缓存 (63)
- 前端aes加密 (58)
- 前端脚手架 (56)
- 前端md5加密 (54)
- 前端路由 (61)
- 前端数组 (73)
- 前端js面试题 (50)
- 前端定时器 (59)
- Oracle RAC (76)
- oracle恢复 (77)
- oracle 删除表 (52)
- oracle 用户名 (80)
- oracle 工具 (55)
- oracle 内存 (55)
- oracle 导出表 (62)
- oracle约束 (54)
- oracle 中文 (51)
- oracle链接 (54)
- oracle的函数 (58)
- oracle面试 (55)
- 前端调试 (52)
本文暂时没有评论,来添加一个吧(●'◡'●)