阿里云主机折上折
  • 微信号
Current Site:Index > Event monitoring mode

Event monitoring mode

Author:Chuan Chen 阅读数:50718人阅读 分类: JavaScript

Event Listening Pattern

The event listening pattern is the core mechanism for handling user interactions in JavaScript. It allows developers to execute predefined functions when specific events occur, enabling dynamic responses. Behaviors such as clicks, scrolling, and keyboard input in the browser environment rely on this pattern.

Basics of Event Listening

DOM elements register event handlers using the addEventListener method:

const button = document.querySelector('#myButton');

button.addEventListener('click', function(event) {
  console.log('Button clicked', event.target);
});

This method takes three parameters:

  1. Event type (e.g., 'click')
  2. Callback function to execute when the event is triggered
  3. Optional configuration object

Event Propagation Mechanism

Events go through three phases in the DOM tree:

  1. Capturing Phase: Propagates from the window object down to the target element
  2. Target Phase: Reaches the target element
  3. Bubbling Phase: Bubbles up from the target element to the window
document.querySelector('.outer').addEventListener('click', () => {
  console.log('Capturing phase', true);
}, true);

document.querySelector('.inner').addEventListener('click', (event) => {
  console.log('Target phase');
  event.stopPropagation(); // Stop event propagation
});

document.body.addEventListener('click', () => {
  console.log('Bubbling phase');
});

Event Delegation Pattern

Leveraging the event bubbling mechanism, events for child elements can be handled uniformly on the parent element:

document.querySelector('#list').addEventListener('click', (event) => {
  if (event.target.matches('li')) {
    console.log('List item clicked:', event.target.textContent);
  }
});

This pattern is particularly suitable for dynamically generated elements, eliminating the need to bind events individually for each child element.

Custom Events

JavaScript allows the creation and triggering of custom events:

// Create event
const customEvent = new CustomEvent('build', {
  detail: { time: new Date() },
  bubbles: true,
  cancelable: true
});

// Listen for event
document.addEventListener('build', (e) => {
  console.log('Custom event triggered:', e.detail);
});

// Trigger event
document.dispatchEvent(customEvent);

Asynchronous Event Handling

Modern JavaScript introduces asynchronous event handling patterns:

const controller = new AbortController();

// Add cancelable event listener
button.addEventListener('click', async () => {
  const data = await fetchData();
  console.log(data);
}, { signal: controller.signal });

// Cancel all related event listeners
controller.abort();

Performance Optimization Techniques

  1. Debouncing: Ensures the event handler executes only once during rapid consecutive triggers
function debounce(fn, delay) {
  let timer;
  return function(...args) {
    clearTimeout(timer);
    timer = setTimeout(() => fn.apply(this, args), delay);
  };
}

window.addEventListener('resize', debounce(() => {
  console.log('Window resize ended');
}, 200));
  1. Throttling: Limits the execution frequency of the event handler
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('Scroll event handled');
}, 100));

Cross-Browser Compatibility

While modern browsers largely follow standards, older versions of IE have different implementations:

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;
  }
}

Event Object Details

The event object received by the event callback function contains rich information:

document.addEventListener('click', (event) => {
  console.log('Coordinates:', event.clientX, event.clientY);
  console.log('Trigger element:', event.target);
  console.log('Current element:', event.currentTarget);
  console.log('Event phase:', event.eventPhase);
  
  // Prevent default behavior
  event.preventDefault();
  
  // Stop event propagation
  event.stopPropagation();
  
  // Immediately stop all processing
  event.stopImmediatePropagation();
});

Event Handling in Modern Frameworks

Frameworks like React encapsulate the native event system:

function MyComponent() {
  const handleClick = useCallback((event) => {
    console.log('React synthetic event:', event.nativeEvent);
  }, []);

  return <button onClick={handleClick}>Click</button>;
}

Vue provides more concise syntax:

<template>
  <button @click="handleClick($event)">Click</button>
</template>

<script>
export default {
  methods: {
    handleClick(event) {
      console.log('Vue event object:', event);
    }
  }
}
</script>

Touch and Gesture Events

Mobile devices support special event types:

const touchArea = document.getElementById('touch-area');

touchArea.addEventListener('touchstart', (e) => {
  const touch = e.touches[0];
  console.log('Touch started:', touch.clientX, touch.clientY);
});

touchArea.addEventListener('touchmove', (e) => {
  e.preventDefault(); // Prevent scrolling
  console.log('Touch moving');
});

touchArea.addEventListener('touchend', () => {
  console.log('Touch ended');
});

Advanced Keyboard Event Handling

Keyboard events can precisely control user input:

document.addEventListener('keydown', (event) => {
  console.log('Key code:', event.keyCode);
  console.log('Key pressed:', event.key);
  
  // Ctrl + S combination
  if (event.ctrlKey && event.key === 's') {
    event.preventDefault();
    saveContent();
  }
});

function saveContent() {
  console.log('Save operation triggered');
}

Drag and Drop API Implementation

HTML5 native drag-and-drop functionality:

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');
});

Page Lifecycle Events

Listening for page state changes:

// Page visibility changes
document.addEventListener('visibilitychange', () => {
  if (document.hidden) {
    console.log('Page hidden');
  } else {
    console.log('Page visible');
  }
});

// Page load states
window.addEventListener('load', () => {
  console.log('All resources loaded');
});

window.addEventListener('DOMContentLoaded', () => {
  console.log('DOM parsed');
});

// Before page unload
window.addEventListener('beforeunload', (e) => {
  e.preventDefault();
  e.returnValue = 'Are you sure you want to leave?';
});

Network Status Events

Detecting network connection changes:

window.addEventListener('online', () => {
  console.log('Network connection restored');
  showNotification('You are back online');
});

window.addEventListener('offline', () => {
  console.log('Network connection lost');
  showNotification('You are offline, some features unavailable');
});

// Using navigator to check current status
if (!navigator.onLine) {
  console.log('Currently offline');
}

Media Query Events

Media query listening in responsive design:

const mediaQuery = window.matchMedia('(max-width: 600px)');

function handleTabletChange(e) {
  if (e.matches) {
    console.log('Switching to mobile layout');
    activateMobileLayout();
  } else {
    console.log('Switching to desktop layout');
    activateDesktopLayout();
  }
}

// Initial check
handleTabletChange(mediaQuery);

// Add listener
mediaQuery.addListener(handleTabletChange);

Web Workers Communication

Event communication with Worker threads:

// Main thread
const worker = new Worker('worker.js');

worker.addEventListener('message', (event) => {
  console.log('Message from Worker:', event.data);
  document.getElementById('result').textContent = event.data;
});

document.getElementById('start').addEventListener('click', () => {
  worker.postMessage('Start calculation');
});

// worker.js
self.addEventListener('message', (e) => {
  if (e.data === 'Start calculation') {
    const result = heavyComputation();
    self.postMessage(result);
  }
});

WebSocket Event Handling

Event model for real-time communication:

const socket = new WebSocket('wss://example.com/socket');

socket.addEventListener('open', () => {
  console.log('Connection established');
  socket.send('Hello Server!');
});

socket.addEventListener('message', (event) => {
  console.log('Message received:', event.data);
  updateUI(JSON.parse(event.data));
});

socket.addEventListener('close', () => {
  console.log('Connection closed');
  attemptReconnect();
});

socket.addEventListener('error', (error) => {
  console.error('WebSocket error:', error);
});

Memory Management for Event Listeners

Key practices to avoid memory leaks:

// Bad practice: Anonymous function cannot be removed
element.addEventListener('click', () => {
  console.log('Click');
});

// Good practice: Use named function
function handleClick() {
  console.log('Click');
}

element.addEventListener('click', handleClick);

// Remove when needed
element.removeEventListener('click', handleClick);

// Batch removal example
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);
  });
}

Passive Event Listeners

Optimization technique to improve scrolling performance:

// Traditional way may block scrolling
window.addEventListener('scroll', () => {
  console.log('Scroll position:', window.scrollY);
});

// Improved with passive
window.addEventListener('scroll', () => {
  console.log('Passive event listener');
}, { passive: true });

// Detect passive support
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;
}

Testing Strategies for Event Listeners

Simulating events in unit tests:

// Using Jest to test event handling
describe('Click event test', () => {
  it('should handle click event', () => {
    const mockHandler = jest.fn();
    const button = document.createElement('button');
    button.addEventListener('click', mockHandler);
    
    // Simulate click
    button.dispatchEvent(new MouseEvent('click', {
      bubbles: true,
      cancelable: true
    }));
    
    expect(mockHandler).toHaveBeenCalledTimes(1);
  });
});

// Testing keyboard events
it('should respond to Enter key', () => {
  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();
});

Underlying Principles of Event Systems

Understanding the internal implementation of event mechanisms:

// Simple event system implementation
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);
    }
  }
}

// Usage example
const emitter = new EventEmitter();
emitter.on('data', (data) => console.log('Data received:', data));
emitter.emit('data', { id: 1, value: 'test' });

Performance Monitoring Events

Using Performance API to monitor key events:

// Monitor long tasks
const observer = new PerformanceObserver((list) => {
  for (const entry of list.getEntries()) {
    console.log('Long task:', entry);
    if (entry.duration > 50) {
      reportLongTask(entry);
    }
  }
});

observer.observe({ entryTypes: ['longtask'] });

// Monitor layout shifts
const layoutObserver = new PerformanceObserver((list) => {
  list.getEntries().forEach((entry) => {
    console.log('Forced synchronous layout:', entry);
  });
});

layoutObserver.observe({ entryTypes: ['layout-shift'] });

Error Handling Events

Global error capture mechanism:

// Global error handling
window.addEventListener('error', (event) => {
  console.error('Global error:', event.error);
  sendErrorToServer(event);
  return true; // Prevent default error handling
});

// Promise rejection capture
window.addEventListener('unhandledrejection', (event) => {
  console.error('Unhandled Promise rejection:', event.reason);
  event.preventDefault(); // Prevent default console error
});

// Resource load failure
window.addEventListener('error', (event) => {
  if (event.target instanceof HTMLElement) {
    console.log('Resource load failed:', event.target.src || event.target.href);
    replaceBrokenImage(event.target);
  }
}, true); // Use capturing phase

Clipboard Event Handling

Safely accessing clipboard content:

document.addEventListener('copy', (event) => {
  const selection = window.getSelection();
  event.clipboardData.setData('text/plain', `Source: My Website\n${selection}`);
  event.preventDefault(); // Prevent default copy behavior
});

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;
    }
  }
});

// Modern async clipboard API
document.getElementById('pasteBtn').addEventListener('click', async () => {
  try {
    const text = await navigator.clipboard.readText();
    console.log('Clipboard text:', text);
  } catch (err) {
    console.error('Clipboard access failed:', err);
  }
});

Fullscreen Change Events

Detecting fullscreen state changes:

document.addEventListener('fullscreenchange', () => {
  if (document.fullscreenElement) {
    console.log('Entered fullscreen mode');
    adjustUIForFullscreen();
  } else {
    console.log('Exited fullscreen mode');
    restoreUI();
  }
});

// Cross-browser compatibility
document.addEventListener('webkitfullscreenchange', handleFullscreenChange);
document.addEventListener('mozfullscreenchange', handleFullscreenChange);
document.addEventListener('MSFullscreenChange', handleFullscreenChange);

// Request fullscreen
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();
  }
});

Device Orientation Events

Handling mobile device sensor data:

// Gyroscope and accelerometer
window.addEventListener('deviceorientation', (event) => {
  console.log('Device orientation:', 
    `alpha: ${event.alpha}`, 
    `beta: ${event.beta}`,
    `gamma: ${event.gamma}`
  );
  updateCompass(event.alpha);
});

window.addEventListener('devicemotion', (event) => {
  const acceleration = event.accelerationIncludingGravity;
  console.log('Device acceleration:', 
    `X: ${acceleration.x}`,
    `Y: ${acceleration.y}`,
    `Z: ${acceleration.z}`
  );
  detectShake(acceleration);
});

// Need to request permission first
function requestMotionPermission() {
  if (typeof DeviceOrientationEvent !== 'undefined' && 
      typeof DeviceOrientationEvent.requestPermission === 'function') {
    DeviceOrientationEvent.requestPermission()
      .then(response => {
        if (response === 'granted') {
          window.addEventListener('deviceorientation', handleOrientation);
        }
      })
      .catch(console.error);
  }
}

Page Visibility API

Optimizing resource usage in background tabs:

document.addEventListener('visibilitychange', () => {
  if (document.hidden) {
    // When page is not visible
    pauseAnimations();
    stopVideoPlayback();
    throttleNetworkRequests();
  } else {
    // When page becomes visible again
    resumeAnimations();
    startVideoPlayback();
    normalNetworkRequests();
  }
});

// Check current state
function logVisibility() {
  console.log('Page visibility state:',
    document.visibilityState,
    `Hidden: ${document.hidden}`
  );
}

// Timer optimization
let timer;
document.addEventListener('visibilitychange', () => {
  if (document.hidden) {
    clearInterval(timer);
  } else {
    timer = setInterval(updateDashboard, 5000);
  }
});

Browser History Events

Listening for navigation behavior:

// Listen for route changes
window.addEventListener('popstate', (event) => {
  console.log('Location changed:', event.state);
  loadContentForRoute(location.pathname);
});

// Listen for hash changes
window.addEventListener('hashchange', () => {
  console.log('Hash changed:', location.hash);
  highlightCurrentSection();

本站部分内容来自互联网,一切版权均归源网站或源作者所有。

如果侵犯了你的权益请来信告知我们删除。邮箱:cc@cccx.cn

上一篇:异步流程控制

下一篇:navigator对象属性

Front End Chuan

Front End Chuan, Chen Chuan's Code Teahouse 🍵, specializing in exorcising all kinds of stubborn bugs 💻. Daily serving baldness-warning-level development insights 🛠️, with a bonus of one-liners that'll make you laugh for ten years 🐟. Occasionally drops pixel-perfect romance brewed in a coffee cup ☕.