Imagine you're designing a smart home control system 🏠 that can respond to everything happening in the house:
The brilliant part? You can connect any sensor to any action, create custom automation chains where one event triggers another, and even have smart delegation where a single control panel manages multiple rooms efficiently.
JavaScript events work exactly like this smart home system. They form the nervous system of web applications, enabling:
Understanding events is crucial for creating interactive, responsive web applications. It's the foundation that makes web pages feel alive and reactive to user input.
Event-driven programming is a fundamental paradigm in computer science where program flow is determined by events rather than sequential execution. This pattern is crucial in:
Core Concepts:
Events implement the Observer Pattern - one of the most important design patterns where:
Why This Pattern Matters:
Event propagation follows tree traversal algorithms:
This design enables:
Event handling has performance implications:
Understanding these concepts helps build performant, scalable event systems.
Events are signals that something has happened in the browser - a user action, a system change, or a custom occurrence that your application needs to respond to.
Mental Model: Think of events like news broadcasts in a city. When something newsworthy happens (an event), it gets broadcast on specific channels (event types), and anyone listening to that channel (event listeners) can respond appropriately.
// Understanding event flow phases
function demonstrateEventFlow() {
const grandparent = document.getElementById('grandparent');
const parent = document.getElementById('parent');
const child = document.getElementById('child');
// Capturing phase listeners (fire from root to target)
grandparent.addEventListener('click', (e) => {
console.log('1. Grandparent - Capturing Phase');
}, true); // true = capturing
parent.addEventListener('click', (e) => {
console.log('2. Parent - Capturing Phase');
}, true);
// Target phase (the actual clicked element)
child.addEventListener('click', (e) => {
console.log('3. Child - Target Phase');
});
// Bubbling phase listeners (fire from target to root)
parent.addEventListener('click', (e) => {
console.log('4. Parent - Bubbling Phase');
}); // false = bubbling (default)
grandparent.addEventListener('click', (e) => {
console.log('5. Grandparent - Bubbling Phase');
});
// When child is clicked, the order will be:
// 1. Grandparent - Capturing Phase
// 2. Parent - Capturing Phase
// 3. Child - Target Phase
// 4. Parent - Bubbling Phase
// 5. Grandparent - Bubbling Phase
}
// Event object properties
function exploreEventObject(event) {
console.log('Event Properties:');
console.log('Type:', event.type); // 'click', 'mouseover', etc.
console.log('Target:', event.target); // Element that triggered the event
console.log('Current Target:', event.currentTarget); // Element with the listener
console.log('Phase:', event.eventPhase); // 1=capturing, 2=target, 3=bubbling
console.log('Bubbles:', event.bubbles); // Can this event bubble?
console.log('Cancelable:', event.cancelable); // Can preventDefault() work?
console.log('Timestamp:', event.timeStamp); // When the event occurred
// Mouse events have additional properties
if (event instanceof MouseEvent) {
console.log('Mouse Properties:');
console.log('Client X/Y:', event.clientX, event.clientY); // Relative to viewport
console.log('Page X/Y:', event.pageX, event.pageY); // Relative to document
console.log('Screen X/Y:', event.screenX, event.screenY); // Relative to screen
console.log('Button:', event.button); // Which mouse button
console.log('Buttons:', event.buttons); // Which buttons are pressed
console.log('Shift Key:', event.shiftKey); // Modifier keys
console.log('Ctrl Key:', event.ctrlKey);
console.log('Alt Key:', event.altKey);
console.log('Meta Key:', event.metaKey); // Cmd on Mac, Windows key on PC
}
// Keyboard events have different properties
if (event instanceof KeyboardEvent) {
console.log('Keyboard Properties:');
console.log('Key:', event.key); // The actual key pressed
console.log('Code:', event.code); // Physical key code
console.log('Key Code:', event.keyCode); // Legacy property
console.log('Char Code:', event.charCode); // Legacy property
console.log('Repeat:', event.repeat); // Is this a repeated key press?
console.log('Location:', event.location); // Key location (left/right/numpad)
}
}
// Event control methods
class EventController {
static stopPropagation(event) {
// Stops the event from bubbling up or capturing down
event.stopPropagation();
console.log('Event propagation stopped');
}
static stopImmediatePropagation(event) {
// Stops all other listeners on the same element from firing
event.stopImmediatePropagation();
console.log('Immediate propagation stopped');
}
static preventDefault(event) {
// Prevents the default browser action
event.preventDefault();
console.log('Default action prevented');
}
static stopAndPrevent(event) {
// Common pattern: stop propagation and prevent default
event.stopPropagation();
event.preventDefault();
}
static handleFormSubmission(event) {
// Prevent form submission for validation
event.preventDefault();
const form = event.target;
const formData = new FormData(form);
// Validate form
if (this.validateForm(formData)) {
// Submit via AJAX instead of default form submission
this.submitFormAjax(formData);
} else {
console.log('Form validation failed');
}
}
static handleLinkClick(event) {
// Prevent default link navigation for SPA routing
event.preventDefault();
const link = event.target.closest('a');
const href = link.getAttribute('href');
// Handle routing in SPA
history.pushState(null, '', href);
this.loadPage(href);
}
static validateForm(formData) {
// Simple validation example
for (let [key, value] of formData.entries()) {
if (!value.trim()) {
alert(`${key} is required`);
return false;
}
}
return true;
}
static submitFormAjax(formData) {
console.log('Submitting form via AJAX:', formData);
// Actual AJAX submission would go here
}
static loadPage(href) {
console.log('Loading page:', href);
// SPA routing logic would go here
}
}
// Usage examples
document.getElementById('myForm').addEventListener('submit', EventController.handleFormSubmission);
document.addEventListener('click', (event) => {
if (event.target.matches('a[href^="#"]')) {
EventController.handleLinkClick(event);
}
});
// Event delegation for efficient event handling
class EventDelegator {
constructor(container) {
this.container = container;
this.delegates = new Map();
this.setupDelegation();
}
// Main delegation handler
setupDelegation() {
this.container.addEventListener('click', (event) => {
this.handleDelegatedEvent('click', event);
});
this.container.addEventListener('change', (event) => {
this.handleDelegatedEvent('change', event);
});
this.container.addEventListener('input', (event) => {
this.handleDelegatedEvent('input', event);
});
// Add more event types as needed
this.container.addEventListener('mouseover', (event) => {
this.handleDelegatedEvent('mouseover', event);
});
this.container.addEventListener('mouseout', (event) => {
this.handleDelegatedEvent('mouseout', event);
});
}
handleDelegatedEvent(eventType, event) {
const delegatesForType = this.delegates.get(eventType);
if (!delegatesForType) return;
// Check each delegation rule
for (let [selector, handler] of delegatesForType) {
if (event.target.matches(selector)) {
// Call handler with proper context
handler.call(event.target, event);
// If handler returned false, stop checking other delegates
if (handler.returnValue === false) {
break;
}
}
}
}
// Register delegation rules
on(eventType, selector, handler) {
if (!this.delegates.has(eventType)) {
this.delegates.set(eventType, new Map());
}
this.delegates.get(eventType).set(selector, handler);
return this; // Enable chaining
}
// Remove delegation rules
off(eventType, selector) {
const delegatesForType = this.delegates.get(eventType);
if (delegatesForType) {
delegatesForType.delete(selector);
}
return this;
}
// Helper for closest ancestor matching
closest(element, selector) {
return element.closest(selector);
}
// Register multiple event handlers
onMultiple(eventTypes, selector, handler) {
eventTypes.forEach(eventType => {
this.on(eventType, selector, handler);
});
return this;
}
}
// Usage example
const app = document.getElementById('app');
const delegator = new EventDelegator(app);
// Handle all button clicks in the app
delegator.on('click', 'button', function(event) {
const action = this.dataset.action;
console.log(`Button clicked: ${action}`);
switch (action) {
case 'save':
handleSave(event);
break;
case 'cancel':
handleCancel(event);
break;
case 'delete':
handleDelete(event);
break;
}
});
// Handle form inputs
delegator.on('input', 'input[data-validate]', function(event) {
const validation = this.dataset.validate;
validateField(this, validation);
});
// Handle dynamic list items (even those added later)
delegator.on('click', '.list-item .remove-btn', function(event) {
const listItem = this.closest('.list-item');
removeListItem(listItem);
});
// Handle hover effects
delegator
.on('mouseover', '.card', function(event) {
this.classList.add('hovered');
})
.on('mouseout', '.card', function(event) {
this.classList.remove('hovered');
});
function handleSave(event) {
console.log('Saving...');
}
function handleCancel(event) {
console.log('Cancelling...');
}
function handleDelete(event) {
if (confirm('Are you sure you want to delete?')) {
console.log('Deleting...');
}
}
function validateField(field, validation) {
console.log(`Validating ${field.name} with rule: ${validation}`);
}
function removeListItem(listItem) {
listItem.remove();
}
// Sophisticated event listener management
class EventManager {
constructor() {
this.listeners = new Map();
this.listenerCount = 0;
}
// Add event listener with automatic cleanup tracking
addListener(element, type, handler, options = {}) {
const listenerId = ++this.listenerCount;
const listenerInfo = {
id: listenerId,
element,
type,
handler,
options,
originalHandler: handler
};
// Wrap handler for additional functionality
const wrappedHandler = this.wrapHandler(handler, listenerInfo);
listenerInfo.wrappedHandler = wrappedHandler;
// Add the actual event listener
element.addEventListener(type, wrappedHandler, options);
// Store for cleanup
this.listeners.set(listenerId, listenerInfo);
return listenerId; // Return ID for removal
}
wrapHandler(originalHandler, listenerInfo) {
return (event) => {
try {
// Add debugging info
if (this.debugMode) {
console.log(`Event: ${event.type} on`, event.target);
}
// Call original handler
const result = originalHandler.call(listenerInfo.element, event);
// Handle once option manually (for older browsers)
if (listenerInfo.options.once) {
this.removeListener(listenerInfo.id);
}
return result;
} catch (error) {
console.error('Event handler error:', error);
// Optionally remove problematic listeners
if (this.removeOnError) {
this.removeListener(listenerInfo.id);
}
}
};
}
// Remove specific listener
removeListener(listenerId) {
const listenerInfo = this.listeners.get(listenerId);
if (listenerInfo) {
listenerInfo.element.removeEventListener(
listenerInfo.type,
listenerInfo.wrappedHandler,
listenerInfo.options
);
this.listeners.delete(listenerId);
return true;
}
return false;
}
// Remove all listeners for an element
removeAllForElement(element) {
const toRemove = [];
for (let [id, info] of this.listeners) {
if (info.element === element) {
toRemove.push(id);
}
}
toRemove.forEach(id => this.removeListener(id));
return toRemove.length;
}
// Remove all listeners of a specific type
removeAllOfType(eventType) {
const toRemove = [];
for (let [id, info] of this.listeners) {
if (info.type === eventType) {
toRemove.push(id);
}
}
toRemove.forEach(id => this.removeListener(id));
return toRemove.length;
}
// Clean up all listeners
cleanup() {
for (let [id] of this.listeners) {
this.removeListener(id);
}
console.log('All event listeners cleaned up');
}
// Get listener statistics
getStats() {
const stats = {
total: this.listeners.size,
byType: {},
byElement: new Map()
};
for (let [id, info] of this.listeners) {
// Count by type
stats.byType[info.type] = (stats.byType[info.type] || 0) + 1;
// Count by element
const element = info.element;
stats.byElement.set(element, (stats.byElement.get(element) || 0) + 1);
}
return stats;
}
// Throttle event handler
throttle(handler, delay = 100) {
let lastCall = 0;
return function(...args) {
const now = Date.now();
if (now - lastCall >= delay) {
lastCall = now;
return handler.apply(this, args);
}
};
}
// Debounce event handler
debounce(handler, delay = 300) {
let timeoutId;
return function(...args) {
clearTimeout(timeoutId);
timeoutId = setTimeout(() => {
handler.apply(this, args);
}, delay);
};
}
// Add throttled listener
addThrottledListener(element, type, handler, delay = 100, options = {}) {
const throttledHandler = this.throttle(handler, delay);
return this.addListener(element, type, throttledHandler, options);
}
// Add debounced listener
addDebouncedListener(element, type, handler, delay = 300, options = {}) {
const debouncedHandler = this.debounce(handler, delay);
return this.addListener(element, type, debouncedHandler, options);
}
// Enable debug mode
enableDebug() {
this.debugMode = true;
}
disableDebug() {
this.debugMode = false;
}
}
// Global event manager instance
const eventManager = new EventManager();
// Usage examples
const button = document.getElementById('myButton');
const input = document.getElementById('searchInput');
// Add regular listener
const clickListenerId = eventManager.addListener(button, 'click', (event) => {
console.log('Button clicked!');
});
// Add throttled scroll listener
const scrollListenerId = eventManager.addThrottledListener(window, 'scroll', (event) => {
console.log('Scrolling...', window.scrollY);
}, 50);
// Add debounced input listener
const inputListenerId = eventManager.addDebouncedListener(input, 'input', (event) => {
console.log('Search query:', event.target.value);
performSearch(event.target.value);
}, 300);
// Remove specific listener
// eventManager.removeListener(clickListenerId);
// Remove all listeners for an element
// eventManager.removeAllForElement(button);
// Get statistics
console.log(eventManager.getStats());
function performSearch(query) {
console.log('Performing search for:', query);
}
// Cleanup on page unload
window.addEventListener('beforeunload', () => {
eventManager.cleanup();
});
// Custom event creation and management
class CustomEventSystem {
constructor() {
this.eventTarget = new EventTarget();
this.eventHistory = [];
this.listeners = new Map();
}
// Create and dispatch a custom event
emit(eventType, detail = null, options = {}) {
const event = new CustomEvent(eventType, {
detail,
bubbles: options.bubbles || false,
cancelable: options.cancelable || false,
...options
});
// Store in history
this.eventHistory.push({
type: eventType,
detail,
timestamp: Date.now(),
bubbles: event.bubbles,
cancelable: event.cancelable
});
// Dispatch the event
this.eventTarget.dispatchEvent(event);
return event;
}
// Listen for custom events
on(eventType, handler) {
this.eventTarget.addEventListener(eventType, handler);
// Track listeners
if (!this.listeners.has(eventType)) {
this.listeners.set(eventType, new Set());
}
this.listeners.get(eventType).add(handler);
return this; // Enable chaining
}
// Remove event listener
off(eventType, handler) {
this.eventTarget.removeEventListener(eventType, handler);
const handlersSet = this.listeners.get(eventType);
if (handlersSet) {
handlersSet.delete(handler);
if (handlersSet.size === 0) {
this.listeners.delete(eventType);
}
}
return this;
}
// Listen once
once(eventType, handler) {
const onceHandler = (event) => {
handler(event);
this.off(eventType, onceHandler);
};
return this.on(eventType, onceHandler);
}
// Remove all listeners for event type
removeAllListeners(eventType) {
const handlersSet = this.listeners.get(eventType);
if (handlersSet) {
for (let handler of handlersSet) {
this.eventTarget.removeEventListener(eventType, handler);
}
this.listeners.delete(eventType);
}
return this;
}
// Get event history
getEventHistory(eventType = null) {
if (eventType) {
return this.eventHistory.filter(event => event.type === eventType);
}
return [...this.eventHistory];
}
// Clear event history
clearHistory() {
this.eventHistory = [];
}
// Get listener count
getListenerCount(eventType = null) {
if (eventType) {
const handlersSet = this.listeners.get(eventType);
return handlersSet ? handlersSet.size : 0;
}
let total = 0;
for (let handlersSet of this.listeners.values()) {
total += handlersSet.size;
}
return total;
}
// Check if event type has listeners
hasListeners(eventType) {
return this.listeners.has(eventType) && this.listeners.get(eventType).size > 0;
}
}
// Application-specific event system
class AppEventBus extends CustomEventSystem {
constructor() {
super();
this.namespaces = new Map();
}
// Namespaced events for better organization
namespace(name) {
if (!this.namespaces.has(name)) {
this.namespaces.set(name, new CustomEventSystem());
}
return this.namespaces.get(name);
}
// Emit application-wide events
emitUserAction(action, data) {
return this.emit('user:action', { action, data });
}
emitStateChange(newState, oldState) {
return this.emit('app:state-change', { newState, oldState });
}
emitError(error, context) {
return this.emit('app:error', { error, context });
}
emitNotification(message, type = 'info') {
return this.emit('app:notification', { message, type });
}
emitDataUpdate(entity, operation, data) {
return this.emit('data:update', { entity, operation, data });
}
// Listen for specific application events
onUserAction(handler) {
return this.on('user:action', handler);
}
onStateChange(handler) {
return this.on('app:state-change', handler);
}
onError(handler) {
return this.on('app:error', handler);
}
onNotification(handler) {
return this.on('app:notification', handler);
}
onDataUpdate(handler) {
return this.on('data:update', handler);
}
}
// Global application event bus
const appEvents = new AppEventBus();
// Usage examples
// Listen for user actions
appEvents.onUserAction((event) => {
const { action, data } = event.detail;
console.log(`User performed action: ${action}`, data);
// Analytics tracking
trackUserAction(action, data);
});
// Listen for state changes
appEvents.onStateChange((event) => {
const { newState, oldState } = event.detail;
console.log('App state changed:', oldState, '->', newState);
// Update UI based on state change
updateUIForState(newState);
});
// Listen for errors
appEvents.onError((event) => {
const { error, context } = event.detail;
console.error('Application error:', error, context);
// Send error to logging service
logError(error, context);
});
// Listen for notifications
appEvents.onNotification((event) => {
const { message, type } = event.detail;
showNotification(message, type);
});
// Emit events
appEvents.emitUserAction('login', { userId: '123', method: 'oauth' });
appEvents.emitStateChange('authenticated', 'anonymous');
appEvents.emitNotification('Login successful!', 'success');
function trackUserAction(action, data) {
console.log('Tracking:', action, data);
}
function updateUIForState(state) {
console.log('Updating UI for state:', state);
}
function logError(error, context) {
console.log('Logging error:', error, context);
}
function showNotification(message, type) {
console.log(`Notification (${type}):`, message);
}
// Component-based event communication system
class Component {
constructor(element) {
this.element = element;
this.eventTarget = new EventTarget();
this.children = new Set();
this.parent = null;
this.eventListeners = [];
this.initialize();
}
initialize() {
// Override in subclasses
}
// Event emitting
emit(eventType, detail = null) {
const event = new CustomEvent(eventType, { detail });
this.eventTarget.dispatchEvent(event);
// Also emit on DOM element for delegation
const domEvent = new CustomEvent(`component:${eventType}`, {
detail: { component: this, ...detail },
bubbles: true
});
this.element.dispatchEvent(domEvent);
return event;
}
// Event listening
on(eventType, handler) {
this.eventTarget.addEventListener(eventType, handler);
this.eventListeners.push({ type: eventType, handler });
return this;
}
off(eventType, handler) {
this.eventTarget.removeEventListener(eventType, handler);
this.eventListeners = this.eventListeners.filter(
listener => !(listener.type === eventType && listener.handler === handler)
);
return this;
}
// Parent-child relationships
addChild(component) {
this.children.add(component);
component.parent = this;
this.emit('child-added', { child: component });
}
removeChild(component) {
this.children.delete(component);
component.parent = null;
this.emit('child-removed', { child: component });
}
// Broadcast to all children
broadcast(eventType, detail) {
for (let child of this.children) {
child.emit(eventType, detail);
child.broadcast(eventType, detail); // Recursive broadcast
}
}
// Send event up to parent
bubble(eventType, detail) {
if (this.parent) {
this.parent.emit(eventType, detail);
}
}
// Cleanup
destroy() {
// Remove all event listeners
this.eventListeners.forEach(({ type, handler }) => {
this.eventTarget.removeEventListener(type, handler);
});
// Remove from parent
if (this.parent) {
this.parent.removeChild(this);
}
// Destroy all children
for (let child of this.children) {
child.destroy();
}
this.emit('destroyed');
}
}
// Example components
class TodoList extends Component {
constructor(element) {
super(element);
this.todos = [];
}
initialize() {
this.setupEventListeners();
}
setupEventListeners() {
// Listen for todo item events
this.element.addEventListener('component:todo-toggle', (event) => {
const { component, completed } = event.detail;
this.handleTodoToggle(component, completed);
});
this.element.addEventListener('component:todo-remove', (event) => {
const { component } = event.detail;
this.handleTodoRemove(component);
});
}
addTodo(text) {
const todoElement = document.createElement('div');
todoElement.className = 'todo-item';
this.element.appendChild(todoElement);
const todoComponent = new TodoItem(todoElement, text);
this.addChild(todoComponent);
this.todos.push(todoComponent);
this.emit('todo-added', { todo: todoComponent });
this.updateStats();
}
handleTodoToggle(todoComponent, completed) {
this.emit('todo-toggled', { todo: todoComponent, completed });
this.updateStats();
}
handleTodoRemove(todoComponent) {
this.removeChild(todoComponent);
this.todos = this.todos.filter(todo => todo !== todoComponent);
this.emit('todo-removed', { todo: todoComponent });
this.updateStats();
todoComponent.destroy();
}
updateStats() {
const total = this.todos.length;
const completed = this.todos.filter(todo => todo.completed).length;
const remaining = total - completed;
this.emit('stats-updated', { total, completed, remaining });
}
clearCompleted() {
const completedTodos = this.todos.filter(todo => todo.completed);
completedTodos.forEach(todo => this.handleTodoRemove(todo));
}
}
class TodoItem extends Component {
constructor(element, text) {
super(element);
this.text = text;
this.completed = false;
this.render();
}
initialize() {
this.setupEventListeners();
}
render() {
this.element.innerHTML = `
<input type="checkbox" class="todo-checkbox">
<span class="todo-text">${this.text}</span>
<button class="todo-remove">×</button>
`;
this.initialize();
}
setupEventListeners() {
const checkbox = this.element.querySelector('.todo-checkbox');
const removeBtn = this.element.querySelector('.todo-remove');
checkbox.addEventListener('change', () => {
this.toggle();
});
removeBtn.addEventListener('click', () => {
this.remove();
});
}
toggle() {
this.completed = !this.completed;
this.element.classList.toggle('completed', this.completed);
this.emit('todo-toggle', { completed: this.completed });
}
remove() {
this.emit('todo-remove');
}
}
// Usage
const todoListElement = document.getElementById('todo-list');
const todoList = new TodoList(todoListElement);
// Listen for todo list events
todoList.on('todo-added', (event) => {
console.log('Todo added:', event.detail.todo);
});
todoList.on('stats-updated', (event) => {
const { total, completed, remaining } = event.detail;
console.log(`Stats: ${completed}/${total} completed, ${remaining} remaining`);
});
// Add some todos
todoList.addTodo('Learn JavaScript events');
todoList.addTodo('Build a todo app');
todoList.addTodo('Master custom events');
// Event pooling for high-frequency events
class EventPool {
constructor(eventType, poolSize = 10) {
this.eventType = eventType;
this.pool = [];
this.inUse = new Set();
// Pre-create events
for (let i = 0; i < poolSize; i++) {
this.pool.push(new CustomEvent(eventType));
}
}
acquire(detail = null) {
let event;
if (this.pool.length > 0) {
event = this.pool.pop();
} else {
event = new CustomEvent(this.eventType);
}
// Reset event properties
if (detail !== null) {
Object.defineProperty(event, 'detail', {
value: detail,
writable: false,
configurable: true
});
}
this.inUse.add(event);
return event;
}
release(event) {
if (this.inUse.has(event)) {
this.inUse.delete(event);
// Clean up event
delete event.detail;
this.pool.push(event);
}
}
getStats() {
return {
poolSize: this.pool.length,
inUse: this.inUse.size,
total: this.pool.length + this.inUse.size
};
}
}
// High-performance event dispatcher
class PerformantEventDispatcher {
constructor() {
this.pools = new Map();
this.listeners = new Map();
this.isDispatching = false;
this.queuedEvents = [];
}
getPool(eventType) {
if (!this.pools.has(eventType)) {
this.pools.set(eventType, new EventPool(eventType));
}
return this.pools.get(eventType);
}
addEventListener(eventType, listener) {
if (!this.listeners.has(eventType)) {
this.listeners.set(eventType, new Set());
}
this.listeners.get(eventType).add(listener);
}
removeEventListener(eventType, listener) {
const listeners = this.listeners.get(eventType);
if (listeners) {
listeners.delete(listener);
}
}
dispatch(eventType, detail = null, immediate = false) {
if (immediate || !this.isDispatching) {
this.dispatchImmediate(eventType, detail);
} else {
// Queue event if currently dispatching (prevent recursion issues)
this.queuedEvents.push({ eventType, detail });
}
}
dispatchImmediate(eventType, detail) {
const pool = this.getPool(eventType);
const event = pool.acquire(detail);
const listeners = this.listeners.get(eventType);
if (listeners && listeners.size > 0) {
this.isDispatching = true;
try {
for (let listener of listeners) {
listener(event);
}
} finally {
this.isDispatching = false;
pool.release(event);
// Process queued events
while (this.queuedEvents.length > 0) {
const queuedEvent = this.queuedEvents.shift();
this.dispatchImmediate(queuedEvent.eventType, queuedEvent.detail);
}
}
} else {
pool.release(event);
}
}
getStats() {
const stats = {
eventTypes: this.listeners.size,
totalListeners: 0,
pools: {}
};
for (let [eventType, listeners] of this.listeners) {
stats.totalListeners += listeners.size;
}
for (let [eventType, pool] of this.pools) {
stats.pools[eventType] = pool.getStats();
}
return stats;
}
}
// Usage for high-frequency events like mouse movement
const dispatcher = new PerformantEventDispatcher();
// Track mouse movement efficiently
let lastMouseEvent = null;
document.addEventListener('mousemove', (event) => {
// Throttle mouse events
if (lastMouseEvent && Date.now() - lastMouseEvent < 16) { // ~60fps
return;
}
lastMouseEvent = Date.now();
dispatcher.dispatch('mouse:move', {
x: event.clientX,
y: event.clientY,
timestamp: lastMouseEvent
});
});
// Listen for mouse events
dispatcher.addEventListener('mouse:move', (event) => {
const { x, y, timestamp } = event.detail;
console.log(`Mouse at ${x}, ${y} at ${timestamp}`);
});
console.log(dispatcher.getStats());
// Event middleware system
class EventMiddleware {
constructor() {
this.middlewares = [];
this.eventTarget = new EventTarget();
}
// Add middleware function
use(middleware) {
this.middlewares.push(middleware);
return this;
}
// Process event through middleware pipeline
async processEvent(eventType, detail, context = {}) {
let currentEvent = {
type: eventType,
detail,
context,
stopped: false,
prevented: false
};
// Process through middleware chain
for (let middleware of this.middlewares) {
if (currentEvent.stopped) break;
try {
const result = await middleware(currentEvent, () => {
// next() function
});
if (result === false) {
currentEvent.stopped = true;
}
} catch (error) {
console.error('Middleware error:', error);
currentEvent.stopped = true;
}
}
// Dispatch final event if not stopped
if (!currentEvent.stopped && !currentEvent.prevented) {
const customEvent = new CustomEvent(eventType, {
detail: currentEvent.detail
});
this.eventTarget.dispatchEvent(customEvent);
}
return currentEvent;
}
// Standard event listener methods
addEventListener(type, listener) {
this.eventTarget.addEventListener(type, listener);
}
removeEventListener(type, listener) {
this.eventTarget.removeEventListener(type, listener);
}
// Convenience method
emit(eventType, detail, context) {
return this.processEvent(eventType, detail, context);
}
}
// Example middleware functions
const loggingMiddleware = (event, next) => {
console.log(`Event: ${event.type}`, event.detail);
next();
};
const authMiddleware = (event, next) => {
if (event.context.requiresAuth && !event.context.user) {
console.log('Authentication required');
event.stopped = true;
return false;
}
next();
};
const validationMiddleware = (event, next) => {
if (event.type === 'user:update' && !event.detail.userId) {
console.log('Validation failed: userId required');
event.stopped = true;
return false;
}
next();
};
const rateLimitMiddleware = (() => {
const limits = new Map();
const windows = new Map();
return (event, next) => {
const key = `${event.type}:${event.context.userId || 'anonymous'}`;
const now = Date.now();
const windowMs = 60000; // 1 minute
const maxRequests = 100;
if (!windows.has(key)) {
windows.set(key, now);
limits.set(key, 1);
} else {
const windowStart = windows.get(key);
if (now - windowStart > windowMs) {
// Reset window
windows.set(key, now);
limits.set(key, 1);
} else {
const currentCount = limits.get(key) + 1;
limits.set(key, currentCount);
if (currentCount > maxRequests) {
console.log('Rate limit exceeded');
event.stopped = true;
return false;
}
}
}
next();
};
})();
// Setup event system with middleware
const eventSystem = new EventMiddleware();
eventSystem
.use(loggingMiddleware)
.use(authMiddleware)
.use(validationMiddleware)
.use(rateLimitMiddleware);
// Listen for processed events
eventSystem.addEventListener('user:update', (event) => {
console.log('Processing user update:', event.detail);
});
eventSystem.addEventListener('user:login', (event) => {
console.log('User logged in:', event.detail);
});
// Emit events
eventSystem.emit('user:login', { username: 'alice' }, { user: null });
eventSystem.emit('user:update', { userId: '123', name: 'Alice' }, {
requiresAuth: true,
user: { id: '123' }
});
eventSystem.emit('user:update', { name: 'Bob' }, {
requiresAuth: true,
user: { id: '456' }
}); // Will fail validation
Events are the nervous system of web applications - they're what makes static pages come alive. Learning to think in terms of event-driven architecture fundamentally changed how I structure applications.
The breakthrough came when I realized that well-designed event systems eliminate tight coupling between components. Instead of components knowing about each other directly, they communicate through events, making applications more modular and maintainable.
The key insight: Events aren't just user interactions - they're a powerful architectural pattern for building scalable, decoupled applications.
Now that you've mastered event handling, we'll explore Fetch API & Network Requests - the modern way to communicate with servers, handle HTTP requests, and build robust network-aware applications.
Remember: Events aren't just about responding to clicks - they're the foundation of interactive, communicative, and modular web applications! 🚀✨
I'm Rahul, Sr. Software Engineer (SDE II) and passionate content creator. Sharing my expertise in software development to assist learners.
More about me