Appearance
JavaScript 事件系统
JavaScript事件系统是实现用户交互和动态行为的核心机制。理解事件的工作原理对于开发响应式Web应用至关重要。
事件类型
鼠标事件
javascript
// click - 鼠标点击(包括触摸屏的点击)
element.addEventListener('click', function(event) {
console.log('Element clicked');
});
// dblclick - 双击
element.addEventListener('dblclick', function(event) {
console.log('Element double-clicked');
});
// mousedown - 鼠标按钮按下
element.addEventListener('mousedown', function(event) {
console.log('Mouse button pressed');
});
// mouseup - 鼠标按钮释放
element.addEventListener('mouseup', function(event) {
console.log('Mouse button released');
});
// mousemove - 鼠标移动
element.addEventListener('mousemove', function(event) {
console.log(`Mouse at: ${event.clientX}, ${event.clientY}`);
});
// mouseover - 鼠标移到元素上(会冒泡)
element.addEventListener('mouseover', function(event) {
console.log('Mouse over element');
});
// mouseout - 鼠标离开元素(会冒泡)
element.addEventListener('mouseout', function(event) {
console.log('Mouse out of element');
});
// mouseenter - 鼠标进入元素(不会冒泡)
element.addEventListener('mouseenter', function(event) {
console.log('Mouse entered element');
});
// mouseleave - 鼠标离开元素(不会冒泡)
element.addEventListener('mouseleave', function(event) {
console.log('Mouse left element');
});
// contextmenu - 右键菜单事件
element.addEventListener('contextmenu', function(event) {
event.preventDefault(); // 阻止默认右键菜单
showCustomMenu(event.clientX, event.clientY);
});
键盘事件
javascript
// keydown - 按键按下(包括修饰键)
document.addEventListener('keydown', function(event) {
console.log(`Key pressed: ${event.key}, Code: ${event.code}`);
// 常用快捷键检测
if (event.ctrlKey && event.key === 's') {
event.preventDefault();
saveDocument();
}
});
// keyup - 按键释放
document.addEventListener('keyup', function(event) {
console.log(`Key released: ${event.key}`);
});
// keypress - 按键按下并产生字符(已废弃,建议使用keydown/keyup)
// 注意:keypress不会触发修饰键、功能键等
表单事件
javascript
const form = document.querySelector('form');
const input = document.querySelector('input');
// input - 输入值改变时触发(实时)
input.addEventListener('input', function(event) {
console.log('Input value:', event.target.value);
});
// change - 元素失去焦点且值改变时触发
input.addEventListener('change', function(event) {
console.log('Value changed to:', event.target.value);
});
// focus - 元素获得焦点
input.addEventListener('focus', function(event) {
event.target.style.backgroundColor = '#e6f3ff';
});
// blur - 元素失去焦点
input.addEventListener('blur', function(event) {
event.target.style.backgroundColor = '';
});
// submit - 表单提交
form.addEventListener('submit', function(event) {
event.preventDefault(); // 阻止默认提交
validateAndSubmitForm();
});
// reset - 表单重置
form.addEventListener('reset', function(event) {
console.log('Form reset');
});
窗口事件
javascript
// load - 页面完全加载完成
window.addEventListener('load', function() {
console.log('Page fully loaded');
});
// DOMContentLoaded - DOM构建完成(不等待图片等资源)
document.addEventListener('DOMContentLoaded', function() {
console.log('DOM ready');
});
// resize - 窗口大小改变
window.addEventListener('resize', function() {
console.log(`Window size: ${window.innerWidth} x ${window.innerHeight}`);
});
// scroll - 滚动事件
window.addEventListener('scroll', function() {
console.log(`Scroll position: ${window.scrollY}`);
});
// beforeunload - 页面即将卸载
window.addEventListener('beforeunload', function(event) {
event.preventDefault();
event.returnValue = 'Are you sure you want to leave?';
});
触摸事件(移动端)
javascript
// touchstart - 触摸开始
element.addEventListener('touchstart', function(event) {
const touch = event.touches[0];
console.log(`Touch started at: ${touch.clientX}, ${touch.clientY}`);
});
// touchmove - 触摸移动
element.addEventListener('touchmove', function(event) {
event.preventDefault(); // 阻止页面滚动
const touch = event.touches[0];
console.log(`Touch moved to: ${touch.clientX}, ${touch.clientY}`);
});
// touchend - 触摸结束
element.addEventListener('touchend', function(event) {
console.log('Touch ended');
});
事件处理方式
HTML事件处理程序(不推荐)
html
<!-- 内联事件处理程序 -->
<button onclick="alert('Hello!')">Click me</button>
<!-- 调用全局函数 -->
<button onclick="handleClick()">Click me</button>
DOM0级事件处理程序
javascript
const button = document.querySelector('button');
// 直接赋值事件处理函数
button.onclick = function() {
console.log('Button clicked');
};
// 也可以赋值函数名
function handleClick() {
console.log('Button clicked');
}
button.onclick = handleClick;
// 移除事件处理程序
button.onclick = null;
DOM2级事件处理程序(推荐)
javascript
const button = document.querySelector('button');
// 添加事件监听器
function handleClick() {
console.log('Button clicked');
}
button.addEventListener('click', handleClick);
// 移除事件监听器(必须使用相同的函数引用)
button.removeEventListener('click', handleClick);
// 可以添加多个相同的事件监听器
button.addEventListener('click', function() {
console.log('First handler');
});
button.addEventListener('click', function() {
console.log('Second handler');
});
// 两个处理器都会执行
事件监听器选项
javascript
element.addEventListener('click', handler, {
once: true, // 只执行一次
passive: true, // 被动模式,不能调用preventDefault
capture: true // 在捕获阶段执行
});
// 简写形式(布尔值)
element.addEventListener('click', handler, true); // 捕获阶段
element.addEventListener('click', handler, false); // 冒泡阶段(默认)
事件对象详解
每个事件处理器都会接收一个事件对象,包含事件的详细信息:
javascript
element.addEventListener('click', function(event) {
// 事件基本信息
console.log('Event type:', event.type); // 'click'
console.log('Target element:', event.target); // 触发事件的实际元素
console.log('Current target:', event.currentTarget); // 当前处理事件的元素
// 事件阶段
console.log('Event phase:', event.eventPhase); // 0:无, 1:捕获, 2:目标, 3:冒泡
// 鼠标信息
console.log('Client coordinates:', event.clientX, event.clientY);
console.log('Page coordinates:', event.pageX, event.pageY);
console.log('Screen coordinates:', event.screenX, event.screenY);
console.log('Button pressed:', event.button); // 0:左键, 1:中键, 2:右键
// 键盘信息
console.log('Key pressed:', event.key);
console.log('Key code:', event.code);
console.log('Ctrl key:', event.ctrlKey);
console.log('Shift key:', event.shiftKey);
console.log('Alt key:', event.altKey);
console.log('Meta key (Cmd/Win):', event.metaKey);
// 事件流控制
event.preventDefault(); // 阻止默认行为
event.stopPropagation(); // 阻止事件冒泡
event.stopImmediatePropagation(); // 阻止其他监听器执行
});
事件阶段与事件流
事件捕获与冒泡
javascript
// HTML结构: <div id="outer"><div id="inner">Click me</div></div>
const outer = document.getElementById('outer');
const inner = document.getElementById('inner');
// 捕获阶段监听器
outer.addEventListener('click', function() {
console.log('Outer - capture phase');
}, true); // 第三个参数为true表示捕获阶段
inner.addEventListener('click', function() {
console.log('Inner - target phase');
}, true); // 仍为捕获阶段
// 冒泡阶段监听器(默认)
inner.addEventListener('click', function() {
console.log('Inner - bubble phase');
}, false); // 第三个参数为false或省略表示冒泡阶段
outer.addEventListener('click', function() {
console.log('Outer - bubble phase');
}, false);
// 点击内部div时的输出顺序:
// 1. "Outer - capture phase"
// 2. "Inner - target phase" (捕获阶段)
// 3. "Inner - bubble phase" (目标阶段,冒泡阶段)
// 4. "Outer - bubble phase" (冒泡阶段)
事件委托实现
javascript
// 利用事件冒泡机制,在父元素上处理子元素事件
document.querySelector('#list-container').addEventListener('click', function(event) {
// 使用事件委托处理动态添加的元素
if (event.target.matches('.list-item')) {
console.log('List item clicked:', event.target.textContent);
}
if (event.target.matches('.delete-btn')) {
event.target.closest('.list-item').remove();
}
if (event.target.matches('a')) {
event.preventDefault(); // 阻止链接跳转
console.log('Link clicked:', event.target.href);
}
});
// 即使后续添加新的列表项,也能正常工作
const newItem = document.createElement('div');
newItem.className = 'list-item';
newItem.textContent = 'Dynamically added item';
document.querySelector('#list-container').appendChild(newItem);
// 点击新添加的项也会触发上面的事件处理器
自定义事件
创建和分发自定义事件
javascript
// 创建自定义事件
const customEvent = new CustomEvent('myCustomEvent', {
detail: {
message: 'Hello from custom event!',
data: { userId: 123, action: 'click' }
},
bubbles: true, // 是否冒泡
cancelable: true // 是否可以取消
});
// 监听自定义事件
element.addEventListener('myCustomEvent', function(event) {
console.log('Custom event received:', event.detail);
console.log('Message:', event.detail.message);
console.log('Data:', event.detail.data);
});
// 分发自定义事件
element.dispatchEvent(customEvent);
// 简单的自定义事件
const simpleEvent = new Event('simpleEvent');
element.addEventListener('simpleEvent', () => console.log('Simple event fired'));
element.dispatchEvent(simpleEvent);
自定义事件的实际应用
javascript
// 组件间通信
class EventEmitter {
constructor() {
this.events = {};
}
on(event, callback) {
if (!this.events[event]) {
this.events[event] = [];
}
this.events[event].push(callback);
}
emit(event, data) {
if (this.events[event]) {
this.events[event].forEach(callback => callback(data));
}
}
off(event, callback) {
if (this.events[event]) {
this.events[event] = this.events[event].filter(cb => cb !== callback);
}
}
}
// 使用事件发射器
const emitter = new EventEmitter();
emitter.on('userLogin', (userData) => {
console.log('User logged in:', userData);
});
emitter.emit('userLogin', { id: 1, name: 'John' });
事件性能优化
防抖(Debounce)
javascript
// 防止函数在短时间内被频繁调用
function debounce(func, delay) {
let timeoutId;
return function(...args) {
clearTimeout(timeoutId);
timeoutId = setTimeout(() => func.apply(this, args), delay);
};
}
// 应用于搜索输入
const searchInput = document.querySelector('#search');
const debouncedSearch = debounce(function(event) {
performSearch(event.target.value);
}, 300);
searchInput.addEventListener('input', debouncedSearch);
节流(Throttle)
javascript
// 限制函数在指定时间间隔内最多执行一次
function throttle(func, limit) {
let inThrottle;
return function(...args) {
if (!inThrottle) {
func.apply(this, args);
inThrottle = true;
setTimeout(() => inThrottle = false, limit);
}
};
}
// 应用于滚动事件
const throttledScroll = throttle(function() {
console.log('Scrolling...', window.scrollY);
}, 100);
window.addEventListener('scroll', throttledScroll);
事件委托优化
javascript
// 不好的做法:为每个按钮添加事件监听器
document.querySelectorAll('.button').forEach(button => {
button.addEventListener('click', handleClick);
});
// 好的做法:使用事件委托
document.addEventListener('click', function(event) {
if (event.target.matches('.button')) {
handleClick(event);
}
});
// 进一步优化:缓存选择器结果
const container = document.querySelector('#button-container');
container.addEventListener('click', function(event) {
if (event.target.matches('.button')) {
handleClick(event);
}
});
现代事件处理模式
使用AbortController移除事件监听器
javascript
// ES2021+ 的新方式
const controller = new AbortController();
element.addEventListener('click', function() {
console.log('Clicked!');
}, { signal: controller.signal });
// 在需要时移除所有相关监听器
controller.abort();
事件处理器类
javascript
class EventHandler {
constructor() {
this.eventMap = new Map();
}
add(element, event, handler, options) {
element.addEventListener(event, handler, options);
// 记录事件以便后续清理
const key = `${element.tagName}-${event}`;
if (!this.eventMap.has(key)) {
this.eventMap.set(key, []);
}
this.eventMap.get(key).push({ element, event, handler, options });
}
removeAll() {
for (const [key, events] of this.eventMap) {
events.forEach(({ element, event, handler }) => {
element.removeEventListener(event, handler);
});
}
this.eventMap.clear();
}
}
// 使用事件处理器
const eventHandler = new EventHandler();
eventHandler.add(button, 'click', handleClick);
// 页面卸载时清理所有事件
window.addEventListener('beforeunload', () => eventHandler.removeAll());
常见事件处理陷阱
this绑定问题
javascript
class Component {
constructor(element) {
this.element = element;
// 错误:this指向不正确
// this.element.addEventListener('click', this.handleClick);
// 解决方案1:使用箭头函数
this.element.addEventListener('click', () => this.handleClick());
// 解决方案2:绑定this
this.element.addEventListener('click', this.handleClick.bind(this));
// 解决方案3:预先绑定
this.boundHandleClick = this.handleClick.bind(this);
this.element.addEventListener('click', this.boundHandleClick);
}
handleClick() {
console.log('Component clicked', this); // 正确的this
}
}
内存泄漏预防
javascript
class Component {
constructor(element) {
this.element = element;
this.boundClickHandler = this.handleClick.bind(this);
this.boundScrollHandler = this.handleScroll.bind(this);
this.element.addEventListener('click', this.boundClickHandler);
window.addEventListener('scroll', this.boundScrollHandler);
}
destroy() {
// 移除事件监听器防止内存泄漏
this.element.removeEventListener('click', this.boundClickHandler);
window.removeEventListener('scroll', this.boundScrollHandler);
// 清理引用
this.element = null;
this.boundClickHandler = null;
this.boundScrollHandler = null;
}
handleClick() {
// 处理点击
}
handleScroll() {
// 处理滚动
}
}
JavaScript事件系统是构建交互式Web应用的基础,理解其工作原理和最佳实践对于开发高质量的应用至关重要。