Appearance
事件处理
事件是用户与网页交互时发生的动作,如点击、输入、滚动等。通过事件处理,JavaScript 可以响应用户的操作。本章将详细介绍事件绑定、事件对象、事件委托等内容。
事件绑定
行内事件(不推荐)
html
<!-- 直接在 HTML 中绑定事件(不推荐) -->
<button onclick="alert('点击了')">点击</button>
<button onclick="handleClick()">点击</button>
<script>
function handleClick() {
console.log('按钮被点击');
}
</script>DOM 属性绑定
javascript
let btn = document.getElementById('btn');
// 将函数赋值给事件属性
btn.onclick = function() {
console.log('按钮被点击');
};
// 使用箭头函数
btn.onclick = () => {
console.log('按钮被点击');
};
// 缺点:只能绑定一个处理函数
btn.onclick = function() {
console.log('第一个处理函数');
};
btn.onclick = function() {
console.log('第二个处理函数'); // 只有这个会执行
};
// 移除事件
btn.onclick = null;addEventListener(推荐)
javascript
let btn = document.getElementById('btn');
// addEventListener(事件类型, 处理函数, 选项)
btn.addEventListener('click', function() {
console.log('按钮被点击');
});
// 可以绑定多个处理函数
btn.addEventListener('click', function() {
console.log('第一个处理函数');
});
btn.addEventListener('click', function() {
console.log('第二个处理函数'); // 两个都会执行
});
// 使用命名函数(便于移除)
function handleClick() {
console.log('点击了');
}
btn.addEventListener('click', handleClick);
// 移除事件监听器
btn.removeEventListener('click', handleClick);
// 使用箭头函数
btn.addEventListener('click', () => {
console.log('箭头函数处理');
});
// 注意:箭头函数无法直接移除(因为是匿名函数)
// 需要保存引用
const handler = () => console.log('点击');
btn.addEventListener('click', handler);
btn.removeEventListener('click', handler);
// 第三个参数:选项对象
btn.addEventListener('click', handleClick, {
capture: false, // 是否在捕获阶段触发
once: true, // 是否只触发一次
passive: true // 是否不会调用 preventDefault
});
// 简写:第三个参数为布尔值表示 capture
btn.addEventListener('click', handleClick, true); // 捕获阶段事件对象
javascript
document.getElementById('btn').addEventListener('click', function(event) {
// event 是事件对象,包含事件的详细信息
// 事件类型
console.log(event.type); // 'click'
// 触发事件的元素
console.log(event.target); // 实际点击的元素
// 绑定事件监听器的元素
console.log(event.currentTarget); // 当前元素
// 事件发生的时间戳
console.log(event.timeStamp);
// 鼠标位置
console.log(event.clientX, event.clientY); // 相对于视口
console.log(event.pageX, event.pageY); // 相对于文档
console.log(event.screenX, event.screenY); // 相对于屏幕
// 鼠标按钮
console.log(event.button); // 0=左键, 1=中键, 2=右键
// 修饰键
console.log(event.ctrlKey); // Ctrl 是否按下
console.log(event.shiftKey); // Shift 是否按下
console.log(event.altKey); // Alt 是否按下
console.log(event.metaKey); // Meta 键(Mac 的 Command)
});阻止默认行为
javascript
// 阻止链接跳转
document.querySelector('a').addEventListener('click', function(e) {
e.preventDefault(); // 阻止默认行为
console.log('链接被点击,但不会跳转');
});
// 阻止表单提交
document.querySelector('form').addEventListener('submit', function(e) {
e.preventDefault();
console.log('表单未提交');
});
// 阻止右键菜单
document.addEventListener('contextmenu', function(e) {
e.preventDefault();
console.log('右键菜单被阻止');
});
// 检查是否调用了 preventDefault
console.log(e.defaultPrevented); // true阻止事件冒泡
javascript
// 事件冒泡:事件从目标元素向上传播
document.getElementById('child').addEventListener('click', function(e) {
console.log('子元素被点击');
e.stopPropagation(); // 阻止事件继续传播
});
document.getElementById('parent').addEventListener('click', function(e) {
console.log('父元素被点击'); // 不会执行
});
// stopImmediatePropagation:阻止后续处理函数
btn.addEventListener('click', function(e) {
console.log('第一个处理函数');
e.stopImmediatePropagation();
});
btn.addEventListener('click', function(e) {
console.log('第二个处理函数'); // 不会执行
});常用事件类型
鼠标事件
javascript
let box = document.getElementById('box');
// 点击事件
box.addEventListener('click', function(e) {
console.log('单击');
});
box.addEventListener('dblclick', function(e) {
console.log('双击');
});
// 鼠标按下/抬起
box.addEventListener('mousedown', function(e) {
console.log('鼠标按下');
});
box.addEventListener('mouseup', function(e) {
console.log('鼠标抬起');
});
// 鼠标移入/移出
box.addEventListener('mouseenter', function(e) {
console.log('鼠标进入(不冒泡)');
});
box.addEventListener('mouseleave', function(e) {
console.log('鼠标离开(不冒泡)');
});
// 鼠标移动(冒泡)
box.addEventListener('mouseover', function(e) {
console.log('鼠标悬停');
});
box.addEventListener('mouseout', function(e) {
console.log('鼠标移出');
});
// 鼠标移动
box.addEventListener('mousemove', function(e) {
console.log('鼠标位置:', e.clientX, e.clientY);
});
// 区别:
// mouseenter/mouseleave:不冒泡,只在元素边界触发
// mouseover/mouseout:冒泡,子元素也会触发键盘事件
javascript
let input = document.getElementById('input');
// 键盘按下
input.addEventListener('keydown', function(e) {
console.log('按下键:', e.key);
console.log('键码:', e.code);
console.log('ASCII 码:', e.keyCode); // 已废弃
console.log('Ctrl 键:', e.ctrlKey);
console.log('Shift 键:', e.shiftKey);
// 阻止某些按键
if (e.key === 'Enter') {
e.preventDefault();
console.log('回车键被阻止');
}
});
// 键盘抬起
input.addEventListener('keyup', function(e) {
console.log('松开键:', e.key);
});
// 字符输入(只对可打印字符有效)
input.addEventListener('keypress', function(e) {
console.log('输入字符:', e.key); // 已废弃
});
// 常用按键检测
document.addEventListener('keydown', function(e) {
// 回车键
if (e.key === 'Enter') {
console.log('按下回车');
}
// ESC 键
if (e.key === 'Escape') {
console.log('按下 ESC');
}
// 方向键
if (e.key === 'ArrowUp') console.log('上');
if (e.key === 'ArrowDown') console.log('下');
if (e.key === 'ArrowLeft') console.log('左');
if (e.key === 'ArrowRight') console.log('右');
// 快捷键
if (e.ctrlKey && e.key === 's') {
e.preventDefault();
console.log('Ctrl+S 保存');
}
});表单事件
javascript
let form = document.querySelector('form');
let input = document.querySelector('input');
// 输入事件(实时触发)
input.addEventListener('input', function(e) {
console.log('输入内容:', e.target.value);
});
// 值变化(失焦时触发)
input.addEventListener('change', function(e) {
console.log('值已改变:', e.target.value);
});
// 获得焦点
input.addEventListener('focus', function(e) {
console.log('获得焦点');
e.target.style.borderColor = 'blue';
});
// 失去焦点
input.addEventListener('blur', function(e) {
console.log('失去焦点');
e.target.style.borderColor = '';
});
// 表单提交
form.addEventListener('submit', function(e) {
e.preventDefault();
console.log('表单提交');
let formData = new FormData(form);
console.log('数据:', Object.fromEntries(formData));
});
// 表单重置
form.addEventListener('reset', function(e) {
console.log('表单重置');
});
// 选择文本
input.addEventListener('select', function(e) {
console.log('选中了文本');
console.log('选中范围:', e.target.selectionStart, e.target.selectionEnd);
});
// 复选框/单选框
let checkbox = document.querySelector('input[type="checkbox"]');
checkbox.addEventListener('change', function(e) {
console.log('选中状态:', e.target.checked);
});
// 下拉框
let select = document.querySelector('select');
select.addEventListener('change', function(e) {
console.log('选中值:', e.target.value);
});文档/窗口事件
javascript
// DOM 加载完成
document.addEventListener('DOMContentLoaded', function() {
console.log('DOM 加载完成');
});
// 页面加载完成(包括图片等资源)
window.addEventListener('load', function() {
console.log('页面加载完成');
});
// 页面卸载前
window.addEventListener('beforeunload', function(e) {
e.preventDefault();
e.returnValue = ''; // 显示确认对话框
});
// 页面卸载
window.addEventListener('unload', function() {
console.log('页面卸载');
});
// 滚动事件
window.addEventListener('scroll', function() {
console.log('滚动位置:', window.scrollY);
});
// 窗口大小改变
window.addEventListener('resize', function() {
console.log('窗口大小:', window.innerWidth, window.innerHeight);
});
// 哈希改变
window.addEventListener('hashchange', function() {
console.log('哈希:', location.hash);
});
// 历史记录改变(pushState/replaceState 不会触发)
window.addEventListener('popstate', function(e) {
console.log('历史记录改变');
});事件委托
什么是事件委托
javascript
// 事件委托:利用事件冒泡,在父元素上统一处理子元素的事件
// 不使用事件委托(需要为每个元素绑定事件)
let items = document.querySelectorAll('li');
items.forEach(item => {
item.addEventListener('click', function() {
console.log('点击了:', this.textContent);
});
});
// 使用事件委托(只需在父元素绑定一次)
let list = document.getElementById('list');
list.addEventListener('click', function(e) {
// 检查点击的是否是 li 元素
if (e.target.tagName === 'LI') {
console.log('点击了:', e.target.textContent);
}
});
// 使用 matches() 方法
list.addEventListener('click', function(e) {
if (e.target.matches('li.item')) {
console.log('点击了 item');
}
});
// 使用 closest() 方法(处理嵌套元素)
list.addEventListener('click', function(e) {
let li = e.target.closest('li');
if (li && list.contains(li)) {
console.log('点击了:', li.textContent);
}
});事件委托的优势
javascript
// 1. 减少事件绑定数量,提高性能
// 不需要为每个子元素绑定事件
// 2. 动态添加的元素也能响应事件
let list = document.getElementById('list');
list.addEventListener('click', function(e) {
if (e.target.tagName === 'LI') {
console.log('点击了:', e.target.textContent);
}
});
// 动态添加的元素也能响应
let newLi = document.createElement('li');
newLi.textContent = '新项目';
list.appendChild(newLi); // 点击也能触发事件
// 3. 方便管理事件
// 所有事件处理逻辑集中在一处事件委托示例
html
<ul id="menu">
<li data-action="home">首页</li>
<li data-action="about">关于</li>
<li data-action="contact">联系</li>
</ul>
<script>
let menu = document.getElementById('menu');
menu.addEventListener('click', function(e) {
let item = e.target.closest('li');
if (!item) return;
let action = item.dataset.action;
switch (action) {
case 'home':
console.log('显示首页');
break;
case 'about':
console.log('显示关于');
break;
case 'contact':
console.log('显示联系');
break;
}
});
</script>自定义事件
创建和触发自定义事件
javascript
// 创建自定义事件
let event = new CustomEvent('myEvent', {
detail: { name: '张三', age: 25 }, // 自定义数据
bubbles: true, // 是否冒泡
cancelable: true // 是否可以取消
});
// 监听自定义事件
document.addEventListener('myEvent', function(e) {
console.log('自定义事件触发了');
console.log('数据:', e.detail);
});
// 触发事件
document.dispatchEvent(event);
// 使用 Event 构造函数(简单事件)
let simpleEvent = new Event('simpleEvent');
document.addEventListener('simpleEvent', function() {
console.log('简单事件');
});
document.dispatchEvent(simpleEvent);自定义事件示例
javascript
// 创建一个简单的计数器组件
class Counter {
constructor(element) {
this.element = element;
this.count = 0;
this.render();
this.bindEvents();
}
render() {
this.element.innerHTML = `
<button class="decrease">-</button>
<span class="count">${this.count}</span>
<button class="increase">+</button>
`;
}
bindEvents() {
this.element.addEventListener('click', (e) => {
if (e.target.matches('.increase')) {
this.count++;
this.update();
} else if (e.target.matches('.decrease')) {
this.count--;
this.update();
}
});
}
update() {
this.element.querySelector('.count').textContent = this.count;
// 触发自定义事件
let event = new CustomEvent('countChange', {
detail: { count: this.count },
bubbles: true
});
this.element.dispatchEvent(event);
}
}
// 使用
let counter = new Counter(document.getElementById('counter'));
// 监听计数变化
document.addEventListener('countChange', function(e) {
console.log('计数变为:', e.detail.count);
});事件循环与异步
javascript
// JavaScript 是单线程的,使用事件循环处理异步操作
// 宏任务(Macro Task)
setTimeout(() => console.log('setTimeout'), 0);
setInterval(() => console.log('setInterval'), 1000);
// 微任务(Micro Task)
Promise.resolve().then(() => console.log('Promise'));
// 执行顺序
console.log('同步代码');
setTimeout(() => console.log('宏任务'), 0);
Promise.resolve().then(() => console.log('微任务'));
console.log('同步代码结束');
// 输出顺序:
// 1. 同步代码
// 2. 同步代码结束
// 3. 微任务
// 4. 宏任务
// 事件循环:同步代码 -> 微任务 -> 宏任务 -> 渲染 -> 微任务 -> ...实战案例
拖拽功能
javascript
let dragElement = document.getElementById('draggable');
let isDragging = false;
let offsetX, offsetY;
dragElement.addEventListener('mousedown', function(e) {
isDragging = true;
offsetX = e.clientX - dragElement.offsetLeft;
offsetY = e.clientY - dragElement.offsetTop;
dragElement.style.cursor = 'grabbing';
});
document.addEventListener('mousemove', function(e) {
if (!isDragging) return;
dragElement.style.left = (e.clientX - offsetX) + 'px';
dragElement.style.top = (e.clientY - offsetY) + 'px';
});
document.addEventListener('mouseup', function() {
isDragging = false;
dragElement.style.cursor = 'grab';
});无限滚动
javascript
let container = document.getElementById('container');
let loading = false;
window.addEventListener('scroll', function() {
// 检查是否滚动到底部
let scrollTop = window.scrollY;
let windowHeight = window.innerHeight;
let documentHeight = document.documentElement.scrollHeight;
if (scrollTop + windowHeight >= documentHeight - 100 && !loading) {
loadMore();
}
});
function loadMore() {
loading = true;
console.log('加载更多...');
// 模拟异步加载
setTimeout(() => {
for (let i = 0; i < 10; i++) {
let item = document.createElement('div');
item.textContent = '新内容 ' + Date.now();
container.appendChild(item);
}
loading = false;
}, 1000);
}键盘导航
javascript
let items = document.querySelectorAll('.nav-item');
let currentIndex = 0;
// 高亮当前项
function highlight(index) {
items.forEach((item, i) => {
item.classList.toggle('active', i === index);
});
}
// 点击选择
items.forEach((item, index) => {
item.addEventListener('click', () => {
currentIndex = index;
highlight(index);
});
});
// 键盘导航
document.addEventListener('keydown', function(e) {
if (e.key === 'ArrowDown' || e.key === 'ArrowRight') {
currentIndex = (currentIndex + 1) % items.length;
highlight(currentIndex);
} else if (e.key === 'ArrowUp' || e.key === 'ArrowLeft') {
currentIndex = (currentIndex - 1 + items.length) % items.length;
highlight(currentIndex);
} else if (e.key === 'Enter') {
items[currentIndex].click();
}
});小结
本章学习了 JavaScript 事件处理的核心知识:
- 事件绑定:行内事件、DOM 属性绑定、addEventListener
- 事件对象:事件类型、目标元素、鼠标位置、修饰键
- 阻止行为:preventDefault、stopPropagation
- 常用事件:鼠标事件、键盘事件、表单事件、文档事件
- 事件委托:利用冒泡机制统一处理事件
- 自定义事件:创建和触发自定义事件
- 事件循环:宏任务、微任务的执行顺序
下一章我们将学习 BOM与定时器,了解浏览器对象模型和定时任务。
