JavaScript 事件
事件是 JavaScript 与用户交互的核心机制。通过事件,JavaScript 可以响应用户的操作(如点击、键盘输入等)以及其他浏览器行为(如页面加载、窗口大小改变等)。掌握事件处理是创建交互式网页应用的关键。在本章节中,我们将深入学习 JavaScript 中的事件系统。
什么是事件
事件是指在网页中发生的特定动作或状态变化,例如:
- 用户点击按钮
- 用户输入文本
- 页面加载完成
- 窗口大小改变
- 鼠标移动
- 键盘按键
JavaScript 可以监听这些事件并在事件发生时执行相应的代码。
事件类型
JavaScript 支持多种类型的事件:
1. 鼠标事件
javascript
// 常见鼠标事件
const button = document.getElementById("myButton");
button.addEventListener("click", function(event) {
console.log("按钮被点击了");
});
button.addEventListener("dblclick", function(event) {
console.log("按钮被双击了");
});
button.addEventListener("mousedown", function(event) {
console.log("鼠标按键按下");
});
button.addEventListener("mouseup", function(event) {
console.log("鼠标按键释放");
});
button.addEventListener("mouseover", function(event) {
console.log("鼠标悬停在按钮上");
});
button.addEventListener("mouseout", function(event) {
console.log("鼠标离开按钮");
});
button.addEventListener("mousemove", function(event) {
console.log("鼠标在按钮上移动");
});2. 键盘事件
javascript
// 常见键盘事件
const input = document.getElementById("myInput");
input.addEventListener("keydown", function(event) {
console.log("按键按下:" + event.key);
});
input.addEventListener("keyup", function(event) {
console.log("按键释放:" + event.key);
});
input.addEventListener("keypress", function(event) {
console.log("按键按住:" + event.key);
});
// 特定按键处理
input.addEventListener("keydown", function(event) {
if (event.key === "Enter") {
console.log("按下了回车键");
}
if (event.ctrlKey && event.key === "s") {
console.log("按下了 Ctrl+S");
event.preventDefault(); // 阻止默认行为
}
});3. 表单事件
javascript
// 表单相关事件
const form = document.getElementById("myForm");
const input = document.getElementById("myInput");
form.addEventListener("submit", function(event) {
console.log("表单提交");
event.preventDefault(); // 阻止表单默认提交
});
input.addEventListener("focus", function(event) {
console.log("输入框获得焦点");
event.target.style.borderColor = "blue";
});
input.addEventListener("blur", function(event) {
console.log("输入框失去焦点");
event.target.style.borderColor = "gray";
});
input.addEventListener("change", function(event) {
console.log("输入框内容改变:" + event.target.value);
});
input.addEventListener("input", function(event) {
console.log("输入框内容实时改变:" + event.target.value);
});4. 窗口事件
javascript
// 窗口相关事件
window.addEventListener("load", function(event) {
console.log("页面加载完成");
});
window.addEventListener("beforeunload", function(event) {
console.log("页面即将卸载");
// 可以显示确认对话框
event.returnValue = "确定要离开此页面吗?";
});
window.addEventListener("resize", function(event) {
console.log("窗口大小改变:" + window.innerWidth + "x" + window.innerHeight);
});
window.addEventListener("scroll", function(event) {
console.log("页面滚动:" + window.scrollY);
});5. 其他事件
javascript
// 文档事件
document.addEventListener("DOMContentLoaded", function(event) {
console.log("DOM 加载完成");
});
// 拖拽事件
const draggable = document.getElementById("draggable");
draggable.addEventListener("dragstart", function(event) {
console.log("开始拖拽");
event.dataTransfer.setData("text/plain", "拖拽的数据");
});
draggable.addEventListener("dragend", function(event) {
console.log("拖拽结束");
});
// 媒体事件
const video = document.getElementById("myVideo");
video.addEventListener("play", function(event) {
console.log("视频开始播放");
});
video.addEventListener("pause", function(event) {
console.log("视频暂停");
});
video.addEventListener("ended", function(event) {
console.log("视频播放结束");
});事件处理方式
1. HTML 内联事件处理器
html
<!-- 不推荐使用 -->
<button onclick="handleClick()">点击我</button>
<input onfocus="handleFocus()" onblur="handleBlur()">javascript
function handleClick() {
console.log("按钮被点击了");
}
function handleFocus() {
console.log("输入框获得焦点");
}
function handleBlur() {
console.log("输入框失去焦点");
}2. DOM 属性事件处理器
javascript
const button = document.getElementById("myButton");
// 设置事件处理器
button.onclick = function(event) {
console.log("按钮被点击了");
};
button.onmouseover = function(event) {
console.log("鼠标悬停");
};
// 移除事件处理器
button.onclick = null;3. addEventListener(推荐)
javascript
const button = document.getElementById("myButton");
// 添加事件监听器
function handleClick(event) {
console.log("按钮被点击了");
}
button.addEventListener("click", handleClick);
// 可以为同一事件添加多个监听器
button.addEventListener("click", function(event) {
console.log("第二个点击处理器");
});
// 移除事件监听器
button.removeEventListener("click", handleClick);事件对象(Event Object)
事件处理器函数接收一个事件对象参数,包含事件的详细信息:
javascript
button.addEventListener("click", function(event) {
console.log("事件类型:" + event.type);
console.log("目标元素:" + event.target);
console.log("当前目标:" + event.currentTarget);
console.log("事件时间戳:" + event.timeStamp);
// 鼠标事件特有属性
if (event.type === "click") {
console.log("鼠标位置 X:" + event.clientX);
console.log("鼠标位置 Y:" + event.clientY);
console.log("按键:" + event.button);
}
// 键盘事件特有属性
if (event.type === "keydown") {
console.log("按键:" + event.key);
console.log("键码:" + event.keyCode);
console.log("是否按下 Ctrl:" + event.ctrlKey);
console.log("是否按下 Alt:" + event.altKey);
console.log("是否按下 Shift:" + event.shiftKey);
}
});事件传播(Event Propagation)
事件传播分为三个阶段:
- 捕获阶段:事件从 document 向下传播到目标元素
- 目标阶段:事件到达目标元素
- 冒泡阶段:事件从目标元素向上传播到 document
html
<div id="parent">
<button id="child">点击我</button>
</div>javascript
const parent = document.getElementById("parent");
const child = document.getElementById("child");
// 捕获阶段监听器
parent.addEventListener("click", function(event) {
console.log("父元素捕获阶段");
}, true); // 第三个参数为 true 表示捕获阶段
// 冒泡阶段监听器
parent.addEventListener("click", function(event) {
console.log("父元素冒泡阶段");
}, false); // 默认为 false,表示冒泡阶段
child.addEventListener("click", function(event) {
console.log("子元素点击");
});
// 输出顺序:
// 父元素捕获阶段
// 子元素点击
// 父元素冒泡阶段阻止事件默认行为和传播
阻止默认行为
javascript
const link = document.getElementById("myLink");
link.addEventListener("click", function(event) {
event.preventDefault(); // 阻止链接跳转
console.log("链接被点击,但不会跳转");
});
const form = document.getElementById("myForm");
form.addEventListener("submit", function(event) {
event.preventDefault(); // 阻止表单提交
console.log("表单提交被阻止");
});阻止事件传播
javascript
const parent = document.getElementById("parent");
const child = document.getElementById("child");
child.addEventListener("click", function(event) {
console.log("子元素点击");
event.stopPropagation(); // 阻止事件继续传播
});
parent.addEventListener("click", function(event) {
console.log("父元素点击"); // 不会执行
});立即阻止事件传播和默认行为
javascript
element.addEventListener("click", function(event) {
console.log("元素点击");
event.stopImmediatePropagation(); // 立即阻止事件传播和同级监听器执行
});自定义事件
JavaScript 允许创建和触发自定义事件:
javascript
// 创建自定义事件
const customEvent = new CustomEvent("myCustomEvent", {
detail: {
message: "这是自定义事件的数据",
timestamp: Date.now()
},
bubbles: true, // 是否冒泡
cancelable: true // 是否可取消
});
// 监听自定义事件
document.addEventListener("myCustomEvent", function(event) {
console.log("接收到自定义事件:", event.detail.message);
console.log("时间戳:", event.detail.timestamp);
});
// 触发自定义事件
document.dispatchEvent(customEvent);
// 带参数的自定义事件
function createAndDispatchEvent(eventName, data) {
const event = new CustomEvent(eventName, {
detail: data
});
document.dispatchEvent(event);
}
// 使用示例
createAndDispatchEvent("userLogin", {
username: "张三",
loginTime: new Date()
});事件委托(Event Delegation)
事件委托利用事件冒泡机制,将事件处理器绑定到父元素上:
html
<ul id="itemList">
<li data-id="1">项目 1</li>
<li data-id="2">项目 2</li>
<li data-id="3">项目 3</li>
</ul>javascript
const itemList = document.getElementById("itemList");
// 使用事件委托处理所有 li 元素的点击事件
itemList.addEventListener("click", function(event) {
// 检查点击的是否是 li 元素
if (event.target.tagName === "LI") {
const itemId = event.target.dataset.id;
console.log("点击了项目:" + itemId);
// 执行相应操作
handleItemClick(itemId);
}
});
function handleItemClick(itemId) {
console.log("处理项目点击,ID:" + itemId);
}
// 动态添加新项目
function addItem(text) {
const newItem = document.createElement("li");
newItem.textContent = text;
newItem.dataset.id = Date.now();
itemList.appendChild(newItem);
// 新项目自动具有点击事件处理能力
}事件性能优化
1. 节流(Throttling)
javascript
// 节流函数
function throttle(func, delay) {
let timeoutId;
let lastExecTime = 0;
return function(...args) {
const currentTime = Date.now();
if (currentTime - lastExecTime > delay) {
func.apply(this, args);
lastExecTime = currentTime;
}
};
}
// 使用节流处理滚动事件
window.addEventListener("scroll", throttle(function(event) {
console.log("滚动事件处理");
}, 100)); // 每100毫秒最多执行一次2. 防抖(Debouncing)
javascript
// 防抖函数
function debounce(func, delay) {
let timeoutId;
return function(...args) {
clearTimeout(timeoutId);
timeoutId = setTimeout(() => {
func.apply(this, args);
}, delay);
};
}
// 使用防抖处理输入事件
const searchInput = document.getElementById("searchInput");
searchInput.addEventListener("input", debounce(function(event) {
console.log("执行搜索:" + event.target.value);
// 执行搜索操作
performSearch(event.target.value);
}, 300)); // 300毫秒内没有新输入才执行3. 事件监听器管理
javascript
// 事件监听器管理器
class EventManager {
constructor() {
this.listeners = new Map();
}
// 添加事件监听器
add(element, eventType, handler, options = {}) {
const key = this._getKey(element, eventType);
if (!this.listeners.has(key)) {
this.listeners.set(key, []);
}
element.addEventListener(eventType, handler, options);
this.listeners.get(key).push({
element: element,
handler: handler,
options: options
});
}
// 移除事件监听器
remove(element, eventType, handler) {
const key = this._getKey(element, eventType);
const listeners = this.listeners.get(key) || [];
element.removeEventListener(eventType, handler);
this.listeners.set(key, listeners.filter(listener =>
listener.handler !== handler
));
}
// 移除所有监听器
removeAll() {
for (let [key, listeners] of this.listeners) {
listeners.forEach(listener => {
listener.element.removeEventListener(
listener.eventType,
listener.handler,
listener.options
);
});
}
this.listeners.clear();
}
// 生成唯一键
_getKey(element, eventType) {
return `${element.constructor.name}_${eventType}`;
}
}
// 使用示例
const eventManager = new EventManager();
const button = document.getElementById("myButton");
const input = document.getElementById("myInput");
eventManager.add(button, "click", function() {
console.log("按钮点击");
});
eventManager.add(input, "input", function() {
console.log("输入改变");
});
// 清理所有事件监听器
// eventManager.removeAll();事件最佳实践
1. 使用现代事件处理方式
javascript
// 推荐:使用 addEventListener
element.addEventListener("click", handleClick);
// 不推荐:使用内联事件处理器
// <button onclick="handleClick()">点击</button>
// 不推荐:使用 DOM 属性
// element.onclick = handleClick;2. 合理使用事件委托
javascript
// 对于动态列表,使用事件委托
const list = document.getElementById("dynamicList");
list.addEventListener("click", function(event) {
if (event.target.classList.contains("delete-btn")) {
// 删除按钮点击
const item = event.target.closest(".list-item");
if (item) {
item.remove();
}
}
});3. 及时清理事件监听器
javascript
// 组件销毁时清理事件监听器
class Component {
constructor() {
this.handleClick = this.handleClick.bind(this);
this.handleResize = this.handleResize.bind(this);
this.init();
}
init() {
this.button.addEventListener("click", this.handleClick);
window.addEventListener("resize", this.handleResize);
}
destroy() {
this.button.removeEventListener("click", this.handleClick);
window.removeEventListener("resize", this.handleResize);
}
handleClick(event) {
// 处理点击
}
handleResize(event) {
// 处理窗口大小改变
}
}4. 事件处理函数的优化
javascript
// 避免在事件处理函数中创建新函数
const button = document.getElementById("myButton");
// 不好的做法
button.addEventListener("click", function(event) {
setTimeout(function() {
console.log("延迟执行");
}, 1000);
});
// 好的做法
function delayedAction() {
console.log("延迟执行");
}
button.addEventListener("click", function(event) {
setTimeout(delayedAction, 1000);
});实际应用示例
1. 模态框组件
javascript
class Modal {
constructor(modalId) {
this.modal = document.getElementById(modalId);
this.overlay = this.modal.querySelector(".modal-overlay");
this.closeButton = this.modal.querySelector(".modal-close");
this.init();
}
init() {
// 绑定事件监听器
this.overlay.addEventListener("click", () => this.close());
this.closeButton.addEventListener("click", () => this.close());
// ESC 键关闭
document.addEventListener("keydown", (event) => {
if (event.key === "Escape" && this.isOpen()) {
this.close();
}
});
}
open() {
this.modal.classList.add("show");
document.body.classList.add("modal-open");
this.dispatchCustomEvent("modal:open");
}
close() {
this.modal.classList.remove("show");
document.body.classList.remove("modal-open");
this.dispatchCustomEvent("modal:close");
}
isOpen() {
return this.modal.classList.contains("show");
}
dispatchCustomEvent(eventName) {
const event = new CustomEvent(eventName, {
detail: { modal: this }
});
this.modal.dispatchEvent(event);
}
}
// 使用示例
const modal = new Modal("myModal");
// 监听自定义事件
document.getElementById("myModal").addEventListener("modal:open", function(event) {
console.log("模态框打开");
});
document.getElementById("myModal").addEventListener("modal:close", function(event) {
console.log("模态框关闭");
});
// 打开模态框
document.getElementById("openModal").addEventListener("click", function() {
modal.open();
});2. 表单验证系统
javascript
class FormValidator {
constructor(formId) {
this.form = document.getElementById(formId);
this.fields = this.form.querySelectorAll("[data-validate]");
this.errors = new Map();
this.init();
}
init() {
// 为每个字段添加验证事件
this.fields.forEach(field => {
const validateOn = field.dataset.validateOn || "blur";
field.addEventListener(validateOn, () => this.validateField(field));
});
// 表单提交验证
this.form.addEventListener("submit", (event) => {
if (!this.validateForm()) {
event.preventDefault();
this.showErrors();
}
});
}
validateField(field) {
const rules = field.dataset.validate.split(" ");
const value = field.value.trim();
let isValid = true;
let errorMessage = "";
for (let rule of rules) {
switch (rule) {
case "required":
if (!value) {
isValid = false;
errorMessage = "此字段为必填项";
}
break;
case "email":
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
if (value && !emailRegex.test(value)) {
isValid = false;
errorMessage = "请输入有效的邮箱地址";
}
break;
case "minLength":
const minLength = parseInt(field.dataset.minLength) || 0;
if (value && value.length < minLength) {
isValid = false;
errorMessage = `最少需要 ${minLength} 个字符`;
}
break;
}
if (!isValid) break;
}
this.setFieldError(field, isValid ? "" : errorMessage);
return isValid;
}
validateForm() {
let isFormValid = true;
this.fields.forEach(field => {
if (!this.validateField(field)) {
isFormValid = false;
}
});
return isFormValid;
}
setFieldError(field, errorMessage) {
const errorElement = field.parentNode.querySelector(".error-message");
if (errorMessage) {
field.classList.add("error");
if (errorElement) {
errorElement.textContent = errorMessage;
}
this.errors.set(field.name, errorMessage);
} else {
field.classList.remove("error");
if (errorElement) {
errorElement.textContent = "";
}
this.errors.delete(field.name);
}
}
showErrors() {
const errorMessages = Array.from(this.errors.values());
if (errorMessages.length > 0) {
alert("请修正以下错误:\n" + errorMessages.join("\n"));
}
}
}
// 使用示例
// const validator = new FormValidator("myForm");总结
JavaScript 事件的核心要点:
- 事件类型:鼠标事件、键盘事件、表单事件、窗口事件等
- 事件处理方式:addEventListener(推荐)、DOM 属性、内联处理器
- 事件对象:包含事件详细信息的对象
- 事件传播:捕获阶段、目标阶段、冒泡阶段
- 事件控制:preventDefault()、stopPropagation()
- 自定义事件:创建和触发自定义事件
- 事件委托:利用事件冒泡处理多个元素事件
- 性能优化:节流、防抖、事件监听器管理
- 最佳实践:及时清理、合理使用事件委托
掌握事件系统是创建交互式网页应用的基础。在下一章节中,我们将学习 JavaScript 的字符串。