事件基础概念
什么是事件
事件是用户或浏览器执行的某种动作,如点击、键盘输入、页面加载等。JavaScript通过事件处理机制来响应这些动作,实现交互功能。
// 事件的基本组成
// 1. 事件源(Event Target):触发事件的元素
// 2. 事件类型(Event Type):事件的名称,如click、keydown等
// 3. 事件处理器(Event Handler):响应事件的函数
// 4. 事件对象(Event Object):包含事件信息的对象
// 基本事件处理示例
const button = document.createElement('button');
button.textContent = '点击我';
document.body.appendChild(button);
// 方式1:HTML属性(不推荐)
// <button onclick="handleClick()">点击我</button>
// 方式2:DOM属性
button.onclick = function(event) {
console.log('按钮被点击了!');
console.log('事件类型:', event.type);
console.log('事件目标:', event.target);
};
// 方式3:addEventListener(推荐)
button.addEventListener('click', function(event) {
console.log('使用addEventListener处理点击事件');
});
// 事件对象的常用属性
button.addEventListener('click', function(event) {
console.log('事件对象属性:');
console.log('type:', event.type); // 事件类型
console.log('target:', event.target); // 事件目标
console.log('currentTarget:', event.currentTarget); // 当前处理事件的元素
console.log('timeStamp:', event.timeStamp); // 事件发生的时间戳
console.log('bubbles:', event.bubbles); // 是否冒泡
console.log('cancelable:', event.cancelable); // 是否可取消
});
事件类型分类
// 创建测试元素
const testDiv = document.createElement('div');
testDiv.style.cssText = `
width: 200px;
height: 100px;
background-color: lightblue;
border: 2px solid blue;
margin: 10px;
padding: 10px;
cursor: pointer;
`;
testDiv.textContent = '测试区域';
document.body.appendChild(testDiv);
const testInput = document.createElement('input');
testInput.type = 'text';
testInput.placeholder = '输入测试';
document.body.appendChild(testInput);
// 1. 鼠标事件
const mouseEvents = {
click: '单击',
dblclick: '双击',
mousedown: '鼠标按下',
mouseup: '鼠标释放',
mousemove: '鼠标移动',
mouseover: '鼠标进入(冒泡)',
mouseout: '鼠标离开(冒泡)',
mouseenter: '鼠标进入(不冒泡)',
mouseleave: '鼠标离开(不冒泡)',
contextmenu: '右键菜单',
wheel: '滚轮滚动'
};
Object.entries(mouseEvents).forEach(([eventType, description]) => {
testDiv.addEventListener(eventType, function(event) {
console.log(`${description} - 坐标: (${event.clientX}, ${event.clientY})`);
// 鼠标事件特有属性
if (event.type === 'click') {
console.log('按钮:', event.button); // 0:左键, 1:中键, 2:右键
console.log('按键组合:', {
ctrlKey: event.ctrlKey,
shiftKey: event.shiftKey,
altKey: event.altKey,
metaKey: event.metaKey
});
}
});
});
// 2. 键盘事件
const keyboardEvents = {
keydown: '按键按下',
keyup: '按键释放',
keypress: '按键按下(已废弃,使用keydown代替)'
};
Object.entries(keyboardEvents).forEach(([eventType, description]) => {
testInput.addEventListener(eventType, function(event) {
console.log(`${description}:`, {
key: event.key, // 按键值
code: event.code, // 物理按键代码
keyCode: event.keyCode, // 按键码(已废弃)
ctrlKey: event.ctrlKey,
shiftKey: event.shiftKey,
altKey: event.altKey
});
// 常用按键检测
if (event.key === 'Enter') {
console.log('回车键被按下');
} else if (event.key === 'Escape') {
console.log('ESC键被按下');
} else if (event.ctrlKey && event.key === 's') {
event.preventDefault(); // 阻止默认保存行为
console.log('Ctrl+S 快捷键');
}
});
});
// 3. 表单事件
const formEvents = {
focus: '获得焦点',
blur: '失去焦点',
input: '输入内容变化',
change: '值改变',
submit: '表单提交',
reset: '表单重置'
};
Object.entries(formEvents).forEach(([eventType, description]) => {
if (eventType === 'input' || eventType === 'change' || eventType === 'focus' || eventType === 'blur') {
testInput.addEventListener(eventType, function(event) {
console.log(`${description} - 当前值:`, event.target.value);
});
}
});
// 4. 窗口和文档事件
const windowEvents = {
load: '页面加载完成',
DOMContentLoaded: 'DOM加载完成',
beforeunload: '页面即将卸载',
unload: '页面卸载',
resize: '窗口大小改变',
scroll: '滚动',
hashchange: 'URL哈希改变',
popstate: '历史状态改变'
};
// 注意:这些事件通常绑定到window或document对象
window.addEventListener('resize', function(event) {
console.log('窗口大小:', window.innerWidth, 'x', window.innerHeight);
});
document.addEventListener('DOMContentLoaded', function(event) {
console.log('DOM加载完成');
});
// 5. 触摸事件(移动设备)
const touchEvents = {
touchstart: '触摸开始',
touchmove: '触摸移动',
touchend: '触摸结束',
touchcancel: '触摸取消'
};
Object.entries(touchEvents).forEach(([eventType, description]) => {
testDiv.addEventListener(eventType, function(event) {
console.log(`${description}:`, {
touches: event.touches.length, // 当前触摸点数量
changedTouches: event.changedTouches.length // 改变的触摸点数量
});
// 阻止默认行为(如滚动)
event.preventDefault();
}, { passive: false }); // passive: false 允许preventDefault
});
事件监听器
addEventListener详解
// addEventListener语法
// element.addEventListener(type, listener, options)
// element.addEventListener(type, listener, useCapture)
const eventButton = document.createElement('button');
eventButton.textContent = '事件测试按钮';
document.body.appendChild(eventButton);
// 基本用法
eventButton.addEventListener('click', function(event) {
console.log('基本点击处理器');
});
// 使用箭头函数
eventButton.addEventListener('click', (event) => {
console.log('箭头函数处理器');
});
// 使用命名函数
function handleClick(event) {
console.log('命名函数处理器');
}
eventButton.addEventListener('click', handleClick);
// options参数详解
eventButton.addEventListener('click', function(event) {
console.log('高级选项处理器');
}, {
once: true, // 只执行一次
passive: false, // 是否为被动监听器
capture: false, // 是否在捕获阶段处理
signal: null // AbortSignal对象,用于取消监听
});
// 使用AbortController取消事件监听
const controller = new AbortController();
const signal = controller.signal;
eventButton.addEventListener('click', function(event) {
console.log('可取消的处理器');
}, { signal });
// 5秒后取消监听
setTimeout(() => {
controller.abort();
console.log('事件监听已取消');
}, 5000);
// 移除事件监听器
eventButton.removeEventListener('click', handleClick);
// 事件监听器管理类
class EventManager {
constructor(element) {
this.element = element;
this.listeners = new Map();
}
on(type, handler, options = {}) {
// 生成唯一标识
const id = this.generateId();
// 包装处理器以便追踪
const wrappedHandler = (event) => {
try {
handler.call(this.element, event);
} catch (error) {
console.error('事件处理器错误:', error);
}
};
// 存储监听器信息
this.listeners.set(id, {
type,
handler: wrappedHandler,
originalHandler: handler,
options
});
// 添加监听器
this.element.addEventListener(type, wrappedHandler, options);
return id; // 返回ID用于后续移除
}
off(id) {
const listener = this.listeners.get(id);
if (listener) {
this.element.removeEventListener(
listener.type,
listener.handler,
listener.options
);
this.listeners.delete(id);
return true;
}
return false;
}
offAll(type) {
let removed = 0;
for (const [id, listener] of this.listeners) {
if (!type || listener.type === type) {
this.off(id);
removed++;
}
}
return removed;
}
once(type, handler, options = {}) {
return this.on(type, handler, { ...options, once: true });
}
emit(type, detail = null) {
const event = new CustomEvent(type, { detail });
this.element.dispatchEvent(event);
return this;
}
getListeners(type) {
const result = [];
for (const [id, listener] of this.listeners) {
if (!type || listener.type === type) {
result.push({ id, ...listener });
}
}
return result;
}
generateId() {
return `listener_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;
}
destroy() {
this.offAll();
this.listeners.clear();
}
}
// 使用事件管理器
const eventManager = new EventManager(eventButton);
// 添加多个监听器
const clickId1 = eventManager.on('click', function(event) {
console.log('管理器处理器1');
});
const clickId2 = eventManager.on('click', function(event) {
console.log('管理器处理器2');
});
const mouseoverId = eventManager.once('mouseover', function(event) {
console.log('鼠标悬停(只执行一次)');
});
// 查看所有监听器
console.log('所有监听器:', eventManager.getListeners());
console.log('点击监听器:', eventManager.getListeners('click'));
// 移除特定监听器
// eventManager.off(clickId1);
// 触发自定义事件
eventManager.emit('custom-event', { message: '自定义数据' });
eventManager.on('custom-event', function(event) {
console.log('自定义事件:', event.detail);
});
事件处理器的this绑定
// 创建测试元素
const thisTestButton = document.createElement('button');
thisTestButton.textContent = 'this绑定测试';
thisTestButton.id = 'this-test';
document.body.appendChild(thisTestButton);
// 1. 普通函数中的this
thisTestButton.addEventListener('click', function(event) {
console.log('普通函数this:', this); // 指向button元素
console.log('this === event.currentTarget:', this === event.currentTarget); // true
});
// 2. 箭头函数中的this
thisTestButton.addEventListener('click', (event) => {
console.log('箭头函数this:', this); // 指向外层作用域的this(通常是window)
});
// 3. 对象方法作为事件处理器
const buttonHandler = {
name: 'ButtonHandler',
handleClick: function(event) {
console.log('对象方法this:', this); // 指向button元素,不是buttonHandler对象
console.log('无法访问name属性:', this.name); // undefined
},
handleClickBound: function(event) {
console.log('绑定后的this:', this); // 指向buttonHandler对象
console.log('可以访问name属性:', this.name); // 'ButtonHandler'
}
};
// 直接使用对象方法(this会被重新绑定)
thisTestButton.addEventListener('click', buttonHandler.handleClick);
// 使用bind绑定this
thisTestButton.addEventListener('click', buttonHandler.handleClickBound.bind(buttonHandler));
// 4. 类中的事件处理
class ButtonController {
constructor(button) {
this.button = button;
this.clickCount = 0;
// 方式1:在构造函数中绑定this
this.handleClick = this.handleClick.bind(this);
button.addEventListener('click', this.handleClick);
// 方式2:使用箭头函数(类字段)
button.addEventListener('click', this.handleClickArrow);
}
handleClick(event) {
this.clickCount++;
console.log(`类方法处理点击,计数: ${this.clickCount}`);
}
// 箭头函数自动绑定this
handleClickArrow = (event) => {
console.log('箭头函数方法,this指向类实例:', this.constructor.name);
}
destroy() {
this.button.removeEventListener('click', this.handleClick);
this.button.removeEventListener('click', this.handleClickArrow);
}
}
// 使用类控制器
const buttonController = new ButtonController(thisTestButton);
// 5. 手动控制this的工具函数
function bindEventHandler(element, eventType, handler, context) {
const boundHandler = function(event) {
return handler.call(context, event);
};
element.addEventListener(eventType, boundHandler);
return function unbind() {
element.removeEventListener(eventType, boundHandler);
};
}
// 使用工具函数
const customContext = {
message: 'Hello from custom context'
};
const unbindCustom = bindEventHandler(
thisTestButton,
'click',
function(event) {
console.log('自定义上下文:', this.message);
},
customContext
);
// 稍后可以取消绑定
// unbindCustom();
事件冒泡和捕获
事件流机制
// 创建嵌套元素结构
const outerDiv = document.createElement('div');
outerDiv.id = 'outer';
outerDiv.style.cssText = `
padding: 30px;
background-color: red;
border: 2px solid darkred;
`;
const middleDiv = document.createElement('div');
middleDiv.id = 'middle';
middleDiv.style.cssText = `
padding: 20px;
background-color: yellow;
border: 2px solid orange;
`;
const innerDiv = document.createElement('div');
innerDiv.id = 'inner';
innerDiv.style.cssText = `
padding: 10px;
background-color: lightblue;
border: 2px solid blue;
cursor: pointer;
`;
innerDiv.textContent = '点击我测试事件流';
// 构建嵌套结构
middleDiv.appendChild(innerDiv);
outerDiv.appendChild(middleDiv);
document.body.appendChild(outerDiv);
// 事件流演示
function createEventLogger(phase) {
return function(event) {
console.log(`${phase} - 元素: ${this.id}, 目标: ${event.target.id}, 当前: ${event.currentTarget.id}`);
};
}
// 1. 捕获阶段(从外到内)
outerDiv.addEventListener('click', createEventLogger('捕获阶段 - outer'), true);
middleDiv.addEventListener('click', createEventLogger('捕获阶段 - middle'), true);
innerDiv.addEventListener('click', createEventLogger('捕获阶段 - inner'), true);
// 2. 冒泡阶段(从内到外)
innerDiv.addEventListener('click', createEventLogger('冒泡阶段 - inner'), false);
middleDiv.addEventListener('click', createEventLogger('冒泡阶段 - middle'), false);
outerDiv.addEventListener('click', createEventLogger('冒泡阶段 - outer'), false);
// 事件流控制
class EventFlowController {
constructor() {
this.logEnabled = true;
}
// 阻止事件冒泡
stopPropagation(event) {
event.stopPropagation();
console.log('事件冒泡已阻止');
}
// 立即阻止事件传播(包括同级监听器)
stopImmediatePropagation(event) {
event.stopImmediatePropagation();
console.log('事件传播已立即阻止');
}
// 阻止默认行为
preventDefault(event) {
event.preventDefault();
console.log('默认行为已阻止');
}
// 同时阻止冒泡和默认行为
stopAll(event) {
event.stopPropagation();
event.preventDefault();
console.log('冒泡和默认行为都已阻止');
}
// 条件性阻止
conditionalStop(event, condition) {
if (condition) {
this.stopPropagation(event);
}
}
}
const flowController = new EventFlowController();
// 添加控制按钮
const controlsDiv = document.createElement('div');
controlsDiv.style.margin = '10px 0';
const stopPropButton = document.createElement('button');
stopPropButton.textContent = '阻止冒泡';
stopPropButton.addEventListener('click', function() {
// 为inner元素添加阻止冒泡的处理器
innerDiv.addEventListener('click', function(event) {
flowController.stopPropagation(event);
}, { once: true });
console.log('下次点击inner元素将阻止冒泡');
});
const stopImmediateButton = document.createElement('button');
stopImmediateButton.textContent = '立即阻止传播';
stopImmediateButton.addEventListener('click', function() {
innerDiv.addEventListener('click', function(event) {
flowController.stopImmediatePropagation(event);
}, { once: true });
console.log('下次点击inner元素将立即阻止传播');
});
controlsDiv.appendChild(stopPropButton);
controlsDiv.appendChild(stopImmediateButton);
document.body.appendChild(controlsDiv);
// 事件流分析工具
class EventFlowAnalyzer {
constructor() {
this.events = [];
this.isRecording = false;
}
startRecording() {
this.events = [];
this.isRecording = true;
console.log('开始记录事件流');
}
stopRecording() {
this.isRecording = false;
console.log('停止记录事件流');
return this.events;
}
recordEvent(phase, element, target, currentTarget) {
if (this.isRecording) {
this.events.push({
timestamp: Date.now(),
phase,
element: element.id || element.tagName,
target: target.id || target.tagName,
currentTarget: currentTarget.id || currentTarget.tagName
});
}
}
analyze() {
console.log('事件流分析:');
this.events.forEach((event, index) => {
console.log(`${index + 1}. ${event.phase} - ${event.element} (目标: ${event.target})`);
});
return {
totalEvents: this.events.length,
capturePhase: this.events.filter(e => e.phase.includes('捕获')).length,
bubblePhase: this.events.filter(e => e.phase.includes('冒泡')).length
};
}
clear() {
this.events = [];
}
}
const analyzer = new EventFlowAnalyzer();
// 为分析器添加监听器
[outerDiv, middleDiv, innerDiv].forEach(element => {
element.addEventListener('click', function(event) {
analyzer.recordEvent('捕获阶段', this, event.target, event.currentTarget);
}, true);
element.addEventListener('click', function(event) {
analyzer.recordEvent('冒泡阶段', this, event.target, event.currentTarget);
}, false);
});
// 分析控制按钮
const analyzeButton = document.createElement('button');
analyzeButton.textContent = '开始分析';
analyzeButton.addEventListener('click', function() {
if (analyzer.isRecording) {
const events = analyzer.stopRecording();
const analysis = analyzer.analyze();
console.log('分析结果:', analysis);
this.textContent = '开始分析';
} else {
analyzer.startRecording();
this.textContent = '停止分析';
}
});
controlsDiv.appendChild(analyzeButton);
事件委托
// 事件委托示例
const listContainer = document.createElement('ul');
listContainer.id = 'task-list';
listContainer.style.cssText = `
border: 1px solid #ccc;
padding: 10px;
margin: 10px 0;
list-style: none;
`;
// 创建初始列表项
for (let i = 1; i <= 3; i++) {
const li = document.createElement('li');
li.innerHTML = `
<span>任务 ${i}</span>
<button class="edit-btn" data-id="${i}">编辑</button>
<button class="delete-btn" data-id="${i}">删除</button>
`;
li.style.cssText = 'margin: 5px 0; padding: 5px; border: 1px solid #eee;';
listContainer.appendChild(li);
}
document.body.appendChild(listContainer);
// 传统方式:为每个按钮添加监听器(不推荐)
function addTraditionalListeners() {
const editButtons = listContainer.querySelectorAll('.edit-btn');
const deleteButtons = listContainer.querySelectorAll('.delete-btn');
editButtons.forEach(button => {
button.addEventListener('click', function(event) {
const id = this.dataset.id;
console.log(`传统方式 - 编辑任务 ${id}`);
});
});
deleteButtons.forEach(button => {
button.addEventListener('click', function(event) {
const id = this.dataset.id;
console.log(`传统方式 - 删除任务 ${id}`);
this.parentElement.remove();
});
});
}
// 事件委托方式(推荐)
listContainer.addEventListener('click', function(event) {
const target = event.target;
// 检查点击的是否是编辑按钮
if (target.classList.contains('edit-btn')) {
const id = target.dataset.id;
console.log(`事件委托 - 编辑任务 ${id}`);
// 编辑逻辑
const span = target.parentElement.querySelector('span');
const currentText = span.textContent;
const newText = prompt('编辑任务:', currentText);
if (newText && newText !== currentText) {
span.textContent = newText;
}
}
// 检查点击的是否是删除按钮
else if (target.classList.contains('delete-btn')) {
const id = target.dataset.id;
console.log(`事件委托 - 删除任务 ${id}`);
if (confirm('确定要删除这个任务吗?')) {
target.parentElement.remove();
}
}
});
// 事件委托管理器
class EventDelegator {
constructor(container) {
this.container = container;
this.delegates = new Map();
this.init();
}
init() {
this.container.addEventListener('click', this.handleClick.bind(this));
this.container.addEventListener('change', this.handleChange.bind(this));
this.container.addEventListener('input', this.handleInput.bind(this));
this.container.addEventListener('submit', this.handleSubmit.bind(this));
}
// 注册委托处理器
delegate(selector, eventType, handler) {
const key = `${eventType}:${selector}`;
if (!this.delegates.has(key)) {
this.delegates.set(key, []);
}
this.delegates.get(key).push(handler);
return this;
}
// 取消委托
undelegate(selector, eventType, handler) {
const key = `${eventType}:${selector}`;
const handlers = this.delegates.get(key);
if (handlers) {
const index = handlers.indexOf(handler);
if (index > -1) {
handlers.splice(index, 1);
}
if (handlers.length === 0) {
this.delegates.delete(key);
}
}
return this;
}
// 处理点击事件
handleClick(event) {
this.executeHandlers('click', event);
}
// 处理change事件
handleChange(event) {
this.executeHandlers('change', event);
}
// 处理input事件
handleInput(event) {
this.executeHandlers('input', event);
}
// 处理submit事件
handleSubmit(event) {
this.executeHandlers('submit', event);
}
// 执行匹配的处理器
executeHandlers(eventType, event) {
for (const [key, handlers] of this.delegates) {
const [type, selector] = key.split(':');
if (type === eventType) {
// 检查事件目标是否匹配选择器
if (event.target.matches(selector)) {
handlers.forEach(handler => {
try {
handler.call(event.target, event);
} catch (error) {
console.error('委托处理器错误:', error);
}
});
}
// 检查事件目标的祖先元素是否匹配选择器
const matchedAncestor = event.target.closest(selector);
if (matchedAncestor && matchedAncestor !== event.target) {
handlers.forEach(handler => {
try {
handler.call(matchedAncestor, event);
} catch (error) {
console.error('委托处理器错误:', error);
}
});
}
}
}
}
// 获取所有委托信息
getDelegates() {
const result = {};
for (const [key, handlers] of this.delegates) {
result[key] = handlers.length;
}
return result;
}
// 清除所有委托
clear() {
this.delegates.clear();
return this;
}
}
// 使用事件委托管理器
const delegator = new EventDelegator(listContainer);
// 注册委托处理器
delegator
.delegate('.edit-btn', 'click', function(event) {
console.log('委托管理器 - 编辑按钮点击');
})
.delegate('.delete-btn', 'click', function(event) {
console.log('委托管理器 - 删除按钮点击');
})
.delegate('li', 'click', function(event) {
console.log('委托管理器 - 列表项点击');
});
// 动态添加新项目的按钮
const addButton = document.createElement('button');
addButton.textContent = '添加新任务';
addButton.addEventListener('click', function() {
const taskCount = listContainer.children.length + 1;
const li = document.createElement('li');
li.innerHTML = `
<span>任务 ${taskCount}</span>
<button class="edit-btn" data-id="${taskCount}">编辑</button>
<button class="delete-btn" data-id="${taskCount}">删除</button>
`;
li.style.cssText = 'margin: 5px 0; padding: 5px; border: 1px solid #eee;';
listContainer.appendChild(li);
console.log('新任务已添加,事件委托自动生效');
});
document.body.appendChild(addButton);
console.log('当前委托信息:', delegator.getDelegates());
自定义事件
创建和触发自定义事件
// 创建测试元素
const customEventDiv = document.createElement('div');
customEventDiv.id = 'custom-event-test';
customEventDiv.style.cssText = `
padding: 20px;
background-color: lightgreen;
border: 2px solid green;
margin: 10px 0;
cursor: pointer;
`;
customEventDiv.textContent = '自定义事件测试区域';
document.body.appendChild(customEventDiv);
// 1. 基本自定义事件
const basicEvent = new Event('myCustomEvent');
// 监听自定义事件
customEventDiv.addEventListener('myCustomEvent', function(event) {
console.log('基本自定义事件被触发');
console.log('事件类型:', event.type);
console.log('是否冒泡:', event.bubbles);
console.log('是否可取消:', event.cancelable);
});
// 触发自定义事件
customEventDiv.dispatchEvent(basicEvent);
// 2. 带数据的自定义事件(CustomEvent)
const dataEvent = new CustomEvent('dataEvent', {
detail: {
message: 'Hello from custom event',
timestamp: Date.now(),
data: { id: 1, name: 'Test' }
},
bubbles: true,
cancelable: true
});
customEventDiv.addEventListener('dataEvent', function(event) {
console.log('带数据的自定义事件:');
console.log('详细信息:', event.detail);
console.log('消息:', event.detail.message);
console.log('数据:', event.detail.data);
});
customEventDiv.dispatchEvent(dataEvent);
// 3. 自定义事件管理器
class CustomEventManager {
constructor(element) {
this.element = element;
this.eventHistory = [];
}
// 创建并触发事件
emit(eventType, detail = null, options = {}) {
const eventOptions = {
bubbles: true,
cancelable: true,
...options
};
let event;
if (detail !== null) {
event = new CustomEvent(eventType, {
detail,
...eventOptions
});
} else {
event = new Event(eventType, eventOptions);
}
// 记录事件历史
this.eventHistory.push({
type: eventType,
detail,
timestamp: Date.now(),
bubbles: event.bubbles,
cancelable: event.cancelable
});
// 触发事件
const result = this.element.dispatchEvent(event);
return {
event,
defaultPrevented: event.defaultPrevented,
propagationStopped: !result
};
}
// 监听事件
on(eventType, handler, options = {}) {
this.element.addEventListener(eventType, handler, options);
return this;
}
// 移除监听
off(eventType, handler, options = {}) {
this.element.removeEventListener(eventType, handler, options);
return this;
}
// 一次性监听
once(eventType, handler, options = {}) {
this.element.addEventListener(eventType, handler, {
...options,
once: true
});
return this;
}
// 获取事件历史
getHistory(eventType = null) {
if (eventType) {
return this.eventHistory.filter(event => event.type === eventType);
}
return [...this.eventHistory];
}
// 清除历史
clearHistory() {
this.eventHistory = [];
return this;
}
// 批量触发事件
emitBatch(events) {
const results = [];
events.forEach(({ type, detail, options }) => {
results.push(this.emit(type, detail, options));
});
return results;
}
// 条件触发
emitIf(condition, eventType, detail, options) {
if (typeof condition === 'function' ? condition() : condition) {
return this.emit(eventType, detail, options);
}
return null;
}
}
// 使用自定义事件管理器
const eventManager = new CustomEventManager(customEventDiv);
// 注册多个监听器
eventManager
.on('user:login', function(event) {
console.log('用户登录事件:', event.detail);
})
.on('user:logout', function(event) {
console.log('用户登出事件:', event.detail);
})
.on('data:update', function(event) {
console.log('数据更新事件:', event.detail);
});
// 触发事件
eventManager.emit('user:login', {
userId: 123,
username: 'john_doe',
loginTime: new Date().toISOString()
});
eventManager.emit('data:update', {
table: 'users',
action: 'insert',
recordId: 456
});
// 批量触发
eventManager.emitBatch([
{
type: 'notification:show',
detail: { message: '欢迎回来!', type: 'success' }
},
{
type: 'analytics:track',
detail: { event: 'page_view', page: '/dashboard' }
}
]);
// 4. 事件总线(全局事件系统)
class EventBus {
constructor() {
this.events = new Map();
this.onceEvents = new Map();
this.middlewares = [];
}
// 添加中间件
use(middleware) {
this.middlewares.push(middleware);
return this;
}
// 监听事件
on(eventType, handler) {
if (!this.events.has(eventType)) {
this.events.set(eventType, []);
}
this.events.get(eventType).push(handler);
return this;
}
// 一次性监听
once(eventType, handler) {
if (!this.onceEvents.has(eventType)) {
this.onceEvents.set(eventType, []);
}
this.onceEvents.get(eventType).push(handler);
return this;
}
// 移除监听
off(eventType, handler) {
if (this.events.has(eventType)) {
const handlers = this.events.get(eventType);
const index = handlers.indexOf(handler);
if (index > -1) {
handlers.splice(index, 1);
}
}
return this;
}
// 触发事件
emit(eventType, ...args) {
// 应用中间件
let eventData = { type: eventType, args, timestamp: Date.now() };
for (const middleware of this.middlewares) {
eventData = middleware(eventData);
if (!eventData) return false; // 中间件可以阻止事件
}
let handled = false;
// 执行普通监听器
if (this.events.has(eventType)) {
const handlers = this.events.get(eventType);
handlers.forEach(handler => {
try {
handler(...eventData.args);
handled = true;
} catch (error) {
console.error(`事件处理器错误 (${eventType}):`, error);
}
});
}
// 执行一次性监听器
if (this.onceEvents.has(eventType)) {
const handlers = this.onceEvents.get(eventType);
handlers.forEach(handler => {
try {
handler(...eventData.args);
handled = true;
} catch (error) {
console.error(`一次性事件处理器错误 (${eventType}):`, error);
}
});
this.onceEvents.delete(eventType); // 清除一次性监听器
}
return handled;
}
// 获取所有事件类型
getEventTypes() {
const types = new Set();
for (const type of this.events.keys()) {
types.add(type);
}
for (const type of this.onceEvents.keys()) {
types.add(type);
}
return Array.from(types);
}
// 清除所有监听器
clear() {
this.events.clear();
this.onceEvents.clear();
return this;
}
// 获取监听器数量
getListenerCount(eventType) {
const regular = this.events.has(eventType) ? this.events.get(eventType).length : 0;
const once = this.onceEvents.has(eventType) ? this.onceEvents.get(eventType).length : 0;
return regular + once;
}
}
// 创建全局事件总线
const globalEventBus = new EventBus();
// 添加日志中间件
globalEventBus.use((eventData) => {
console.log(`[EventBus] ${eventData.type} 事件被触发:`, eventData.args);
return eventData;
});
// 添加过滤中间件
globalEventBus.use((eventData) => {
// 过滤掉某些事件
if (eventData.type.startsWith('debug:') && !window.DEBUG_MODE) {
return null; // 阻止事件
}
return eventData;
});
// 使用事件总线
globalEventBus
.on('app:start', function() {
console.log('应用启动');
})
.on('user:action', function(action, data) {
console.log(`用户操作: ${action}`, data);
})
.once('app:ready', function() {
console.log('应用就绪(只执行一次)');
});
// 触发事件
globalEventBus.emit('app:start');
globalEventBus.emit('user:action', 'click', { button: 'submit' });
globalEventBus.emit('app:ready');
globalEventBus.emit('app:ready'); // 不会再次执行
console.log('事件总线状态:');
console.log('所有事件类型:', globalEventBus.getEventTypes());
console.log('user:action 监听器数量:', globalEventBus.getListenerCount('user:action'));
// 点击测试区域触发自定义事件
customEventDiv.addEventListener('click', function() {
eventManager.emit('user:click', {
x: Math.random() * 100,
y: Math.random() * 100,
timestamp: Date.now()
});
globalEventBus.emit('ui:interaction', 'click', {
element: 'custom-event-div'
});
});
console.log('事件历史:', eventManager.getHistory());
本章总结
本章深入探讨了JavaScript的事件处理机制:
- 事件基础:理解了事件的组成要素、事件类型分类和事件对象的属性
- 事件监听器:掌握了addEventListener的使用方法、选项参数和this绑定
- 事件流:学习了事件的捕获和冒泡机制,以及如何控制事件传播
- 事件委托:了解了事件委托的原理和优势,以及如何实现高效的事件管理
- 自定义事件:掌握了创建、触发和管理自定义事件的方法
- 高级模式:通过事件管理器、事件总线等模式展示了企业级事件处理方案
事件处理是Web应用交互的核心,熟练掌握这些概念和技术能够帮助我们构建响应迅速、用户体验良好的应用程序。这些知识为后续学习框架和库的事件系统奠定了重要基础。
下一章我们将学习异步编程的高级主题,包括Promise链式调用、async/await最佳实践和错误处理策略。