事件监听模式
事件监听模式
事件监听模式是JavaScript中处理用户交互的核心机制。它允许开发者在特定事件发生时执行预定义的函数,从而实现动态响应。浏览器环境中的点击、滚动、键盘输入等行为都依赖这种模式。
事件监听基础
DOM元素通过addEventListener
方法注册事件处理器:
const button = document.querySelector('#myButton');
button.addEventListener('click', function(event) {
console.log('按钮被点击', event.target);
});
这个方法接收三个参数:
- 事件类型(如'click')
- 事件触发时执行的回调函数
- 可选的配置对象
事件传播机制
事件在DOM树中经历三个阶段:
- 捕获阶段:从window对象向下传播到目标元素
- 目标阶段:到达事件目标元素
- 冒泡阶段:从目标元素向上冒泡到window
document.querySelector('.outer').addEventListener('click', () => {
console.log('捕获阶段', true);
}, true);
document.querySelector('.inner').addEventListener('click', (event) => {
console.log('目标阶段');
event.stopPropagation(); // 阻止事件传播
});
document.body.addEventListener('click', () => {
console.log('冒泡阶段');
});
事件委托模式
利用事件冒泡机制,可以在父元素上统一处理子元素的事件:
document.querySelector('#list').addEventListener('click', (event) => {
if (event.target.matches('li')) {
console.log('点击了列表项:', event.target.textContent);
}
});
这种模式特别适合动态生成的元素,无需为每个子元素单独绑定事件。
自定义事件
JavaScript允许创建和触发自定义事件:
// 创建事件
const customEvent = new CustomEvent('build', {
detail: { time: new Date() },
bubbles: true,
cancelable: true
});
// 监听事件
document.addEventListener('build', (e) => {
console.log('自定义事件触发:', e.detail);
});
// 触发事件
document.dispatchEvent(customEvent);
异步事件处理
现代JavaScript引入了异步事件处理模式:
const controller = new AbortController();
// 添加可取消的事件监听
button.addEventListener('click', async () => {
const data = await fetchData();
console.log(data);
}, { signal: controller.signal });
// 取消所有相关事件监听
controller.abort();
性能优化技巧
- 防抖:确保事件处理器在快速连续触发时只执行一次
function debounce(fn, delay) {
let timer;
return function(...args) {
clearTimeout(timer);
timer = setTimeout(() => fn.apply(this, args), delay);
};
}
window.addEventListener('resize', debounce(() => {
console.log('窗口大小调整结束');
}, 200));
- 节流:限制事件处理器的执行频率
function throttle(fn, interval) {
let lastTime = 0;
return function(...args) {
const now = Date.now();
if (now - lastTime >= interval) {
fn.apply(this, args);
lastTime = now;
}
};
}
document.addEventListener('scroll', throttle(() => {
console.log('滚动事件处理');
}, 100));
跨浏览器兼容性
虽然现代浏览器基本遵循标准,但旧版本IE有不同实现:
function addEvent(element, type, handler) {
if (element.addEventListener) {
element.addEventListener(type, handler, false);
} else if (element.attachEvent) {
element.attachEvent('on' + type, handler);
} else {
element['on' + type] = handler;
}
}
事件对象详解
事件回调函数接收的事件对象包含丰富信息:
document.addEventListener('click', (event) => {
console.log('坐标:', event.clientX, event.clientY);
console.log('触发元素:', event.target);
console.log('当前元素:', event.currentTarget);
console.log('事件阶段:', event.eventPhase);
// 阻止默认行为
event.preventDefault();
// 阻止事件传播
event.stopPropagation();
// 立即停止所有处理
event.stopImmediatePropagation();
});
现代框架中的事件处理
React等框架封装了原生事件系统:
function MyComponent() {
const handleClick = useCallback((event) => {
console.log('React合成事件:', event.nativeEvent);
}, []);
return <button onClick={handleClick}>点击</button>;
}
Vue提供了更简洁的语法:
<template>
<button @click="handleClick($event)">点击</button>
</template>
<script>
export default {
methods: {
handleClick(event) {
console.log('Vue事件对象:', event);
}
}
}
</script>
触摸和手势事件
移动端设备支持特殊事件类型:
const touchArea = document.getElementById('touch-area');
touchArea.addEventListener('touchstart', (e) => {
const touch = e.touches[0];
console.log('触摸开始:', touch.clientX, touch.clientY);
});
touchArea.addEventListener('touchmove', (e) => {
e.preventDefault(); // 阻止滚动
console.log('触摸移动');
});
touchArea.addEventListener('touchend', () => {
console.log('触摸结束');
});
键盘事件深度处理
键盘事件可以精确控制用户输入:
document.addEventListener('keydown', (event) => {
console.log('键码:', event.keyCode);
console.log('按键:', event.key);
// Ctrl + S 组合键
if (event.ctrlKey && event.key === 's') {
event.preventDefault();
saveContent();
}
});
function saveContent() {
console.log('保存操作被触发');
}
拖放API实现
HTML5原生拖放功能:
const draggable = document.getElementById('draggable');
const dropzone = document.getElementById('dropzone');
draggable.addEventListener('dragstart', (e) => {
e.dataTransfer.setData('text/plain', draggable.id);
draggable.classList.add('dragging');
});
dropzone.addEventListener('dragover', (e) => {
e.preventDefault();
dropzone.classList.add('drag-over');
});
dropzone.addEventListener('drop', (e) => {
e.preventDefault();
const id = e.dataTransfer.getData('text/plain');
const element = document.getElementById(id);
dropzone.appendChild(element);
dropzone.classList.remove('drag-over');
});
页面生命周期事件
监听页面状态变化:
// 页面可见性变化
document.addEventListener('visibilitychange', () => {
if (document.hidden) {
console.log('页面被隐藏');
} else {
console.log('页面可见');
}
});
// 页面加载状态
window.addEventListener('load', () => {
console.log('所有资源加载完成');
});
window.addEventListener('DOMContentLoaded', () => {
console.log('DOM解析完成');
});
// 页面卸载前
window.addEventListener('beforeunload', (e) => {
e.preventDefault();
e.returnValue = '确定离开吗?';
});
网络状态事件
检测网络连接变化:
window.addEventListener('online', () => {
console.log('网络连接恢复');
showNotification('您已恢复网络连接');
});
window.addEventListener('offline', () => {
console.log('网络连接断开');
showNotification('您已离线,部分功能不可用');
});
// 使用navigator检测当前状态
if (!navigator.onLine) {
console.log('当前处于离线状态');
}
媒体查询事件
响应式设计中的媒体查询监听:
const mediaQuery = window.matchMedia('(max-width: 600px)');
function handleTabletChange(e) {
if (e.matches) {
console.log('切换到移动布局');
activateMobileLayout();
} else {
console.log('切换到桌面布局');
activateDesktopLayout();
}
}
// 初始检查
handleTabletChange(mediaQuery);
// 添加监听
mediaQuery.addListener(handleTabletChange);
Web Workers通信
与Worker线程的事件通信:
// 主线程
const worker = new Worker('worker.js');
worker.addEventListener('message', (event) => {
console.log('收到Worker消息:', event.data);
document.getElementById('result').textContent = event.data;
});
document.getElementById('start').addEventListener('click', () => {
worker.postMessage('开始计算');
});
// worker.js
self.addEventListener('message', (e) => {
if (e.data === '开始计算') {
const result = heavyComputation();
self.postMessage(result);
}
});
WebSocket事件处理
实时通信的事件模型:
const socket = new WebSocket('wss://example.com/socket');
socket.addEventListener('open', () => {
console.log('连接已建立');
socket.send('Hello Server!');
});
socket.addEventListener('message', (event) => {
console.log('收到消息:', event.data);
updateUI(JSON.parse(event.data));
});
socket.addEventListener('close', () => {
console.log('连接已关闭');
attemptReconnect();
});
socket.addEventListener('error', (error) => {
console.error('WebSocket错误:', error);
});
事件监听的内存管理
避免内存泄漏的关键实践:
// 不好的做法:匿名函数无法移除
element.addEventListener('click', () => {
console.log('点击');
});
// 正确做法:使用具名函数
function handleClick() {
console.log('点击');
}
element.addEventListener('click', handleClick);
// 需要时移除
element.removeEventListener('click', handleClick);
// 批量移除示例
const events = [
{ element: button1, type: 'click', handler: handleClick },
{ element: button2, type: 'mouseover', handler: handleHover }
];
function cleanupEvents() {
events.forEach(({ element, type, handler }) => {
element.removeEventListener(type, handler);
});
}
被动事件监听器
提升滚动性能的优化技巧:
// 传统方式可能阻塞滚动
window.addEventListener('scroll', () => {
console.log('滚动位置:', window.scrollY);
});
// 使用passive改进
window.addEventListener('scroll', () => {
console.log('被动事件监听');
}, { passive: true });
// 检测passive支持
let passiveSupported = false;
try {
const options = {
get passive() {
passiveSupported = true;
return false;
}
};
window.addEventListener('test', null, options);
window.removeEventListener('test', null, options);
} catch (err) {
passiveSupported = false;
}
事件监听的测试策略
单元测试中的事件模拟:
// 使用Jest测试事件处理
describe('点击事件测试', () => {
it('应该处理点击事件', () => {
const mockHandler = jest.fn();
const button = document.createElement('button');
button.addEventListener('click', mockHandler);
// 模拟点击
button.dispatchEvent(new MouseEvent('click', {
bubbles: true,
cancelable: true
}));
expect(mockHandler).toHaveBeenCalledTimes(1);
});
});
// 测试键盘事件
it('应该响应回车键', () => {
const input = document.createElement('input');
const mockHandler = jest.fn();
input.addEventListener('keydown', (e) => {
if (e.key === 'Enter') mockHandler();
});
input.dispatchEvent(new KeyboardEvent('keydown', { key: 'Enter' }));
expect(mockHandler).toHaveBeenCalled();
});
事件系统的底层原理
了解事件机制的内部实现:
// 简单的事件系统实现
class EventEmitter {
constructor() {
this.events = {};
}
on(type, listener) {
this.events[type] = this.events[type] || [];
this.events[type].push(listener);
}
emit(type, ...args) {
if (this.events[type]) {
this.events[type].forEach(listener => listener(...args));
}
}
off(type, listener) {
if (this.events[type]) {
this.events[type] = this.events[type].filter(l => l !== listener);
}
}
}
// 使用示例
const emitter = new EventEmitter();
emitter.on('data', (data) => console.log('收到数据:', data));
emitter.emit('data', { id: 1, value: 'test' });
性能监控事件
使用Performance API监听关键事件:
// 监听长任务
const observer = new PerformanceObserver((list) => {
for (const entry of list.getEntries()) {
console.log('长任务:', entry);
if (entry.duration > 50) {
reportLongTask(entry);
}
}
});
observer.observe({ entryTypes: ['longtask'] });
// 监听布局变化
const layoutObserver = new PerformanceObserver((list) => {
list.getEntries().forEach((entry) => {
console.log('强制同步布局:', entry);
});
});
layoutObserver.observe({ entryTypes: ['layout-shift'] });
错误处理事件
全局错误捕获机制:
// 全局错误处理
window.addEventListener('error', (event) => {
console.error('全局错误:', event.error);
sendErrorToServer(event);
return true; // 阻止默认错误处理
});
// Promise拒绝捕获
window.addEventListener('unhandledrejection', (event) => {
console.error('未处理的Promise拒绝:', event.reason);
event.preventDefault(); // 阻止默认控制台报错
});
// 资源加载失败
window.addEventListener('error', (event) => {
if (event.target instanceof HTMLElement) {
console.log('资源加载失败:', event.target.src || event.target.href);
replaceBrokenImage(event.target);
}
}, true); // 使用捕获阶段
剪贴板事件处理
安全地访问剪贴板内容:
document.addEventListener('copy', (event) => {
const selection = window.getSelection();
event.clipboardData.setData('text/plain', `来源: 我的网站\n${selection}`);
event.preventDefault(); // 阻止默认复制行为
});
document.addEventListener('paste', async (event) => {
const items = event.clipboardData.items;
for (let i = 0; i < items.length; i++) {
if (items[i].type.indexOf('image') !== -1) {
const blob = items[i].getAsFile();
const imageUrl = URL.createObjectURL(blob);
displayPastedImage(imageUrl);
break;
}
}
});
// 现代异步剪贴板API
document.getElementById('pasteBtn').addEventListener('click', async () => {
try {
const text = await navigator.clipboard.readText();
console.log('剪贴板文本:', text);
} catch (err) {
console.error('剪贴板访问失败:', err);
}
});
全屏变化事件
检测全屏状态变化:
document.addEventListener('fullscreenchange', () => {
if (document.fullscreenElement) {
console.log('进入全屏模式');
adjustUIForFullscreen();
} else {
console.log('退出全屏模式');
restoreUI();
}
});
// 跨浏览器兼容
document.addEventListener('webkitfullscreenchange', handleFullscreenChange);
document.addEventListener('mozfullscreenchange', handleFullscreenChange);
document.addEventListener('MSFullscreenChange', handleFullscreenChange);
// 请求全屏
document.getElementById('fullscreenBtn').addEventListener('click', () => {
const elem = document.documentElement;
if (elem.requestFullscreen) {
elem.requestFullscreen();
} else if (elem.webkitRequestFullscreen) {
elem.webkitRequestFullscreen();
} else if (elem.mozRequestFullScreen) {
elem.mozRequestFullScreen();
} else if (elem.msRequestFullscreen) {
elem.msRequestFullscreen();
}
});
设备方向事件
处理移动设备传感器数据:
// 陀螺仪和加速度计
window.addEventListener('deviceorientation', (event) => {
console.log('设备方向:',
`alpha: ${event.alpha}`,
`beta: ${event.beta}`,
`gamma: ${event.gamma}`
);
updateCompass(event.alpha);
});
window.addEventListener('devicemotion', (event) => {
const acceleration = event.accelerationIncludingGravity;
console.log('设备加速度:',
`X: ${acceleration.x}`,
`Y: ${acceleration.y}`,
`Z: ${acceleration.z}`
);
detectShake(acceleration);
});
// 需要先请求权限
function requestMotionPermission() {
if (typeof DeviceOrientationEvent !== 'undefined' &&
typeof DeviceOrientationEvent.requestPermission === 'function') {
DeviceOrientationEvent.requestPermission()
.then(response => {
if (response === 'granted') {
window.addEventListener('deviceorientation', handleOrientation);
}
})
.catch(console.error);
}
}
页面可见性API
优化后台标签页的资源使用:
document.addEventListener('visibilitychange', () => {
if (document.hidden) {
// 页面不可见时
pauseAnimations();
stopVideoPlayback();
throttleNetworkRequests();
} else {
// 页面恢复可见
resumeAnimations();
startVideoPlayback();
normalNetworkRequests();
}
});
// 检测当前状态
function logVisibility() {
console.log('页面可见性状态:',
document.visibilityState,
`隐藏: ${document.hidden}`
);
}
// 定时器优化
let timer;
document.addEventListener('visibilitychange', () => {
if (document.hidden) {
clearInterval(timer);
} else {
timer = setInterval(updateDashboard, 5000);
}
});
浏览器历史事件
监听导航行为:
// 监听路由变化
window.addEventListener('popstate', (event) => {
console.log('位置变化:', event.state);
loadContentForRoute(location.pathname);
});
// 监听hash变化
window.addEventListener('hashchange', () => {
console.log('hash变化:', location.hash);
highlightCurrentSection();
本站部分内容来自互联网,一切版权均归源网站或源作者所有。
如果侵犯了你的权益请来信告知我们删除。邮箱:cc@cccx.cn
上一篇:异步流程控制
下一篇:navigator对象属性