Mastering DOM Events: From Basics to Advanced Event Flow

This article explains the fundamentals of DOM events, compares three ways to attach listeners, shows how to add and remove handlers, details event bubbling and capturing phases, and provides practical tips and common pitfalls for robust frontend interaction design.

FunTester
FunTester
FunTester
Mastering DOM Events: From Basics to Advanced Event Flow

Introduction

Event handling is pervasive in frontend development; a good design directly influences code maintainability and user experience. This article focuses on DOM event basics and the event flow mechanism.

DOM Event Basics

What Is an Event

An event is a specific action triggered by the user or the browser, such as click, mousemove, load, or input. By listening to events and writing handler functions, a page can respond to these actions.

Three Ways to Listen to Events

Method 1: Inline HTML (Not Recommended)

<button onclick="handleClick()">点击我</button>
<script>
function handleClick() {
  alert('按钮被点击了!');
}
</script>

Disadvantage: strong coupling between HTML and JavaScript, making maintenance and reuse difficult.

Method 2: DOM Property

<button id="myButton">点击我</button>
<script>
const button = document.getElementById('myButton');
button.onclick = function() {
  alert('按钮被点击了!');
};
</script>

Disadvantage: only one handler can be bound to the same event.

Method 3: addEventListener (Recommended)

<button id="myButton">点击我</button>
<script>
const button = document.getElementById('myButton');

// Can bind multiple handlers
button.addEventListener('click', function() {
  console.log('第一个处理函数');
});

button.addEventListener('click', function() {
  console.log('第二个处理函数');
});
</script>

Advantages:

Multiple handlers can be bound.

Control over the capture or bubble phase.

Precise removal of listeners.

Removing Event Listeners

function handleClick() {
  console.log('点击了!');
}
const button = document.getElementById('myButton');
button.addEventListener('click', handleClick);

// Remove listener (must use the same function reference)
button.removeEventListener('click', handleClick);

Common mistake: using an anonymous function makes removal impossible.

// ❌ Wrong: cannot remove anonymous function
button.addEventListener('click', function() {
  console.log('点击了!');
});
button.removeEventListener('click', function() {
  console.log('点击了!');
});

// ✅ Correct: use a named function or store the reference
const handler = function() {
  console.log('点击了!');
};
button.addEventListener('click', handler);
button.removeEventListener('click', handler);

Event Bubbling and Capturing

The Three Phases of Event Flow

1. Capture phase: from window down to the target element
2. Target phase: reaches the target element
3. Bubble phase: from the target element up to window

Basic API: addEventListener(type, listener, useCapture): the third argument true triggers in the capture phase; default false triggers in the bubble phase. removeEventListener(type, listener, useCapture): parameters must match exactly when removing.

Example Demonstration

<div id="outer">
  外层 DIV
  <div id="inner">
    内层 DIV
    <button id="button">点击我</button>
  </div>
</div>

<script>
const outer = document.getElementById('outer');
const inner = document.getElementById('inner');
const button = document.getElementById('button');

// Bubble phase (default)
button.addEventListener('click', () => {
  console.log('按钮 - 冒泡');
});
inner.addEventListener('click', () => {
  console.log('内层 DIV - 冒泡');
});
outer.addEventListener('click', () => {
  console.log('外层 DIV - 冒泡');
});

// Capture phase (third argument true)
outer.addEventListener('click', () => {
  console.log('外层 DIV - 捕获');
}, true);
inner.addEventListener('click', () => {
  console.log('内层 DIV - 捕获');
}, true);
button.addEventListener('click', () => {
  console.log('按钮 - 捕获');
}, true);
</script>

Preventing Propagation and Default Behavior

// Stop bubbling
button.addEventListener('click', (e) => {
  console.log('按钮被点击');
  e.stopPropagation();
});

// Stop other listeners on the same element
button.addEventListener('click', (e) => {
  console.log('第一个监听器');
  e.stopImmediatePropagation();
});
button.addEventListener('click', () => {
  console.log('第二个监听器 - 不会执行');
});

// Prevent default action (e.g., link navigation)
const link = document.querySelector('a');
link.addEventListener('click', (e) => {
  e.preventDefault();
  console.log('链接被点击,但不会跳转');
});

Applicable Scenarios and Recommendations

Complex nested interactions: use capture/bubble wisely and avoid redundant listeners on deep nodes.

List or table clicks: prefer event delegation to reduce bindings and memory overhead.

Component encapsulation: handle events at the component root and expose clear callbacks.

Common Pitfalls

Using anonymous functions prevents removeEventListener from working.

Binding events inside a var loop can cause index errors.

Misusing stopImmediatePropagation affects other listeners on the same element.

Forgetting to clean up listeners leads to memory leaks.

JavaScriptfrontend developmentDOMevent handlingEvent PropagationaddEventListener
FunTester
Written by

FunTester

10k followers, 1k articles | completely useless

0 followers
Reader feedback

How this landed with the community

Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.