🎯 课程目标

完成本课程后,你将能够:

  • 理解JavaScript事件模型的基本原理
  • 掌握各种事件类型的用法
  • 能够为元素绑定和移除事件监听器
  • 理解事件对象和事件冒泡机制
  • 能够实现事件委托优化性能

📖 事件概述

事件是浏览器和用户交互的桥梁。当用户点击按钮、按下键盘、滚动页面时,都会触发相应的事件。通过事件处理,我们可以对用户的操作做出响应,实现交互式的网页。

✨ 常见事件类型

  • 鼠标事件:click、dblclick、mouseenter、mouseleave、mousedown、mouseup
  • 键盘事件:keydown、keyup、keypress
  • 表单事件:submit、change、input、focus、blur
  • 窗口事件:load、resize、scroll、unload
  • 触摸事件:touchstart、touchend、touchmove(移动端)

🎧 事件监听器

使用addEventListener方法为元素绑定事件处理函数。

基本用法

const button = document.querySelector('#myButton'); // 方法1:使用addEventListener(推荐) button.addEventListener('click', function() { console.log('按钮被点击了!'); }); // 方法2:使用箭头函数 button.addEventListener('click', () => { console.log('按钮被点击了!'); }); // 方法3:外部定义函数 function handleClick() { console.log('按钮被点击了!'); } button.addEventListener('click', handleClick); // 移除事件监听器 button.removeEventListener('click', handleClick); // 重要:addEventListener的选项 button.addEventListener('click', handler, { once: true, // 只触发一次 passive: true, // 声明为被动(不调用preventDefault) capture: true // 在捕获阶段处理 });

旧版事件绑定方式(不推荐)

const button = document.querySelector('#myButton'); // HTML内联事件(不推荐,难以维护) <button onclick="handleClick()">点击</button> // DOM属性绑定(只能绑定一个处理函数) button.onclick = function() { console.log('点击'); }; // 缺点:无法同时绑定多个处理函数,会覆盖 button.onclick = function() { console.log('这个会覆盖上面的'); };

🖱️ 鼠标事件

const box = document.querySelector('.box'); // 单击事件(最常用) box.addEventListener('click', (e) => { console.log('点击位置:', e.clientX, e.clientY); }); // 双击事件 box.addEventListener('dblclick', () => { console.log('双击!'); }); // 鼠标进入/离开 box.addEventListener('mouseenter', () => { box.style.backgroundColor = 'lightblue'; }); box.addEventListener('mouseleave', () => { box.style.backgroundColor = ''; }); // 鼠标按下/抬起 box.addEventListener('mousedown', () => { console.log('鼠标按下'); }); box.addEventListener('mouseup', () => { console.log('鼠标抬起'); }); // 鼠标移动(常用于拖拽) box.addEventListener('mousemove', (e) => { console.log('鼠标位置:', e.clientX, e.clientY); }); // 阻止默认右键菜单 box.addEventListener('contextmenu', (e) => { e.preventDefault(); console.log('右键点击'); });

⌨️ 键盘事件

const input = document.querySelector('input[type="text"]'); // 键盘按下(所有按键) input.addEventListener('keydown', (e) => { console.log('按键:', e.key); console.log('按键码:', e.code); console.log('按键值:', e.keyCode); // 已废弃,但仍可用 }); // 键盘抬起 input.addEventListener('keyup', (e) => { console.log('按键释放:', e.key); }); // 字符输入(仅字符键) input.addEventListener('keypress', (e) => { console.log('输入字符:', e.key); }); // 常用按键判断 input.addEventListener('keydown', (e) => { // Ctrl+C 复制 if (e.ctrlKey && e.key === 'c') { console.log('复制操作被阻止'); e.preventDefault(); } // Enter 提交 if (e.key === 'Enter') { console.log('回车提交'); } // Escape 取消 if (e.key === 'Escape') { console.log('取消操作'); } // F5 刷新 if (e.key === 'F5') { e.preventDefault(); } });

📝 表单事件

const form = document.querySelector('form'); const input = document.querySelector('#username'); // 表单提交事件 form.addEventListener('submit', (e) => { e.preventDefault(); // 阻止表单默认提交行为 console.log('表单提交'); // 验证通过后手动提交 // form.submit(); }); // 输入事件(实时) input.addEventListener('input', (e) => { console.log('当前值:', e.target.value); // 实时验证 if (e.target.value.length < 3) { console.log('太短'); } }); // 变化事件(失去焦点时触发) input.addEventListener('change', (e) => { console.log('值已改变:', e.target.value); }); // 获得/失去焦点 input.addEventListener('focus', () => { input.style.borderColor = 'blue'; }); input.addEventListener('blur', () => { input.style.borderColor = ''; }); // 重置事件 form.addEventListener('reset', (e) => { console.log('表单重置'); // 可以在这里添加确认提示 // if (!confirm('确定要重置吗?')) { // e.preventDefault(); // } });

🌊 事件对象详解

事件处理函数会接收一个事件对象参数,包含事件的详细信息。

document.addEventListener('click', (e) => { // 事件类型 console.log(e.type); // "click" // 目标元素(触发事件的元素) console.log(e.target); // 当前元素(绑定了事件处理程序的元素) console.log(e.currentTarget); // 鼠标/键盘位置 console.log('页面坐标:', e.pageX, e.pageY); // 相对于页面 console.log('窗口坐标:', e.clientX, e.clientY); // 相对于视口 console.log('相对于目标:', e.offsetX, e.offsetY); // 相对于目标元素 // 修饰键状态 console.log('Shift:', e.shiftKey); console.log('Ctrl:', e.ctrlKey); console.log('Alt:', e.altKey); console.log('Meta:', e.metaKey); // Windows键或Command键 // 默认行为 // e.preventDefault(); // 阻止默认行为 // e.stopPropagation(); // 阻止事件冒泡 // e.stopImmediatePropagation(); // 阻止后续监听器执行 });

📊 事件冒泡与捕获

事件有三个阶段:捕获阶段、目标阶段、冒泡阶段。理解这个机制对于正确处理事件至关重要。

<!-- HTML结构 --> <div id="parent"> <button id="child">点击我</button> </div> <script> const parent = document.getElementById('parent'); const child = document.getElementById('child'); // 事件冒泡(默认,第三个参数为false) parent.addEventListener('click', () => { console.log('父元素 - 冒泡阶段'); }); child.addEventListener('click', () => { console.log('子元素 - 目标阶段'); }); // 事件捕获(第三个参数为true) parent.addEventListener('click', () => { console.log('父元素 - 捕获阶段'); }, true); // 点击子元素时,输出顺序: // 1. 父元素 - 捕获阶段 // 2. 子元素 - 目标阶段 // 3. 父元素 - 冒泡阶段 // 阻止事件冒泡 child.addEventListener('click', (e) => { console.log('子元素被点击'); e.stopPropagation(); // 阻止冒泡,父元素不会收到事件 }); </script>

⚡ 事件委托

事件委托是一种优化技术,将事件监听器绑定到父元素,利用事件冒泡处理子元素的事件。这样可以减少事件监听器数量,支持动态添加的元素。

<ul id="todoList"> <li>任务1 <button class="delete">删除</button></li> <li>任务2 <button class="delete">删除</button></li> <li>任务3 <button class="delete">删除</button></li> </ul> <button id="addItem">添加任务</button> <script> const list = document.getElementById('todoList'); // 传统方式:为每个删除按钮绑定事件 // 问题:新增的元素没有事件监听器 document.querySelectorAll('.delete').forEach(btn => { btn.addEventListener('click', function() { this.parentElement.remove(); }); }); // 事件委托:绑定到父元素 list.addEventListener('click', (e) => { // 检查点击的是否是删除按钮 if (e.target.classList.contains('delete')) { e.target.parentElement.remove(); } // 检查点击的是否是任务项(用于切换完成状态) if (e.target.tagName === 'LI') { e.target.classList.toggle('completed'); } }); // 新增任务也会自动有事件 document.getElementById('addItem').addEventListener('click', () => { const li = document.createElement('li'); li.textContent = '新任务'; li.innerHTML += ' <button class="delete">删除</button>'; list.appendChild(li); }); </script>
💡 事件委托的优点:减少内存占用、支持动态元素、代码更简洁

⚠️ 常见问题

问题原因分析解决方案
事件不触发元素不存在或事件绑定时机错误确保DOM加载完成后再绑定
this指向错误箭头函数的this与普通函数不同使用e.currentTarget替代this
重复触发事件重复添加事件监听器使用flag标记或once选项
性能问题过多事件监听器使用事件委托优化
移动端事件不响应触摸事件与鼠标事件差异使用touch事件或添加touch-action样式

✅ 课后练习

练习要求:请独立完成以下练习任务:

  1. 练习 1:创建一个可拖拽的方块,使用mousedown、mousemove、mouseup事件
  2. 练习 2:实现一个实时搜索框,使用input事件和防抖优化
  3. 选做练习:创建一个可排序的表格,支持点击表头排序