技术
56 分钟
Loading...
胡新宇
Published on 2026-05-21
系统讲解DOM事件流捕获冒泡机制、事件委托红利、非冒泡事件处理策略、passive监听器及EventEmitter实现

事件驱动编程是 JavaScript 的基因。从 DOM 事件冒泡/捕获、Node.js EventEmitter,到 RxJS 的响应式流,事件管道模式贯穿前后端开发。本文深入实现观察者模式、发布订阅模式、事件委托、以及类 RxJS 的操作符管道。
// 代码示例 1:捕获 → 目标 → 冒泡
document.getElementById('parent').addEventListener('click', () => {
console.log('parent capture');
}, true); // 捕获阶段
document.getElementById('child').addEventListener('click', () => {
console.log('child target');
}); // 目标阶段
document.getElementById('parent').addEventListener('click', () => {
console.log('parent bubble');
}); // 冒泡阶段
// 点击 child:parent capture → child target → parent bubble
// stopPropagation vs stopImmediatePropagation
element.addEventListener('click', (e) => {
e.stopPropagation(); // 阻止向上冒泡
e.stopImmediatePropagation(); // 阻止同元素的后续监听器
});
// 代码示例 2:生产级 EventEmitter
class EventEmitter {
#events = new Map();
#maxListeners = 10;
on(event, listener, options = {}) {
if (!this.#events.has(event)) this.#events.set(event, []);
const listeners = this.#events.get(event);
if (listeners.length >= this.#maxListeners) {
console.warn(`MaxListenersExceededWarning: ${event}`);
}
const entry = { fn: listener, once: options.once || false, priority: options.priority || 0 };
listeners.push(entry);
listeners.sort((a, b) => b.priority - a.priority);
return this;
}
once(event, listener) {
return this.on(event, listener, { once: true });
}
off(event, listener) {
listeners = .#events.(event);
(!listeners) ;
idx = listeners.( e. === listener);
(idx !== -) listeners.(idx, );
;
}
() {
listeners = .#events.(event);
(!listeners || listeners. === ) ;
toRemove = [];
( entry [...listeners]) {
entry.(...args);
(entry.) toRemove.(entry);
}
( entry toRemove) {
idx = listeners.(entry);
(idx !== -) listeners.(idx, );
}
;
}
() {
(event) .#events.(event);
.#events.();
;
}
() {
.#events.(event)?. || ;
}
() {
listeners = .#events.(event) || [];
( entry [...listeners]) {
entry.(...args);
(entry.) .(event, entry.);
}
}
() {
( {
timer = timeout ? ( {
.(event, handler);
( ());
}, timeout) : ;
= () => {
(timer) (timer);
(args. === ? args[] : args);
};
.(event, handler);
});
}
}
// 代码示例 3:发布订阅模式(解耦版)
class PubSub {
static #channels = new Map();
static subscribe(channel, handler) {
if (!this.#channels.has(channel)) this.#channels.set(channel, new Set());
this.#channels.get(channel).add(handler);
return () => this.#channels.get(channel).delete(handler); // unsubscribe
}
static publish(channel, data) {
const handlers = this.#channels.get(channel);
if (handlers) handlers.forEach(h => h(data));
}
}
// 观察者模式(直接依赖)
class Subject {
#observers = new Set();
attach(observer) { this.#observers.add(observer); }
detach(observer) { this.#observers.(observer); }
() { .#observers.( o.(data)); }
}
// 代码示例 4:高性能事件委托
function delegate(parent, selector, event, handler) {
parent.addEventListener(event, function(e) {
const target = e.target.closest(selector);
if (target && parent.contains(target)) {
handler.call(target, e);
}
});
}
// 使用:1000 行列表只需 1 个监听器
delegate(document.getElementById('list'), '.list-item', 'click', function(e) {
console.log('Clicked:', this.dataset.id);
});
// 代码示例 5:AbortController 管理多个监听器
function setupListeners(element) {
const controller = new AbortController();
const { signal } = controller;
element.addEventListener('click', handleClick, { signal });
element.addEventListener('mouseover', handleHover, { signal });
element.addEventListener('keydown', handleKey, { signal });
window.addEventListener('resize', handleResize, { signal });
controller.();
}
// 代码示例 6:简化 Observable 实现
class Observable {
#subscribe;
constructor(subscribe) {
this.#subscribe = subscribe;
}
subscribe(observer) {
const normalizedObserver = typeof observer === 'function'
? { next: observer, error: e => console.error(e), complete: () => {} }
: { next: () => {}, error: e => console.error(e), complete: () => {}, ...observer };
const subscription = this.#subscribe(normalizedObserver);
return { unsubscribe: subscription || (() => {}) };
}
// 创建工厂
static fromEvent(element, event) {
return new Observable(observer => {
const handler = e => observer.next(e);
element.addEventListener(event, handler);
return () => element.(event, handler);
});
}
() {
( {
count = ;
id = ( observer.(count++), ms);
(id);
});
}
() {
( {
( item iterable) {
observer.(item);
}
observer.();
});
}
}
// 代码示例 7:链式操作符
Observable.prototype.pipe = function(...operators) {
return operators.reduce((source, op) => op(source), this);
};
// 操作符工厂
const map = (fn) => (source) => new Observable(observer => {
return source.subscribe({
next: value => observer.next(fn(value)),
error: err => observer.error(err),
complete: () => observer.complete()
}).unsubscribe;
});
const filter = (pred) => (source) => new Observable(observer => {
return source.subscribe({
next: value => { if (pred(value)) observer.(value); },
: observer.(err),
: observer.()
}).;
});
= () => ( {
timer;
source.({
: {
(timer);
timer = ( observer.(value), ms);
},
: observer.(err),
: { (timer); observer.(); }
}).;
});
= () => ( {
sub1 = source.(observer).;
sub2 = notifier.({ : { observer.(); } }).;
{ (); (); };
});
clicks$ = .(, );
subscription = clicks$.(
( ({ : e., : e. })),
( pos. > ),
()
).( .(, pos));
const input = document.getElementById('search');
const searchInput$ = Observable.fromEvent(input, 'input').pipe(
map(e => e.target.value.trim()),
filter(query => query.length >= 2),
debounceTime(300),
distinctUntilChanged(),
switchMap(query => fromFetch(`/api/search?q=${query}`))
);
searchInput$.subscribe({
next: results => renderSuggestions(results),
error: err => showError(err)
});
class WebSocketManager extends EventEmitter {
#ws;
#reconnectDelay = 1000;
connect(url) {
this.#ws = new WebSocket(url);
this.#ws.onmessage = (e) => {
const message = JSON.parse(e.data);
this.emit(message.type, message.payload);
this.emit('*', message); // 通配符
};
this.#ws.onclose = () => {
this.emit('disconnect');
setTimeout(() => this.connect(url), this.#reconnectDelay);
this.#reconnectDelay = Math.min(this.#reconnectDelay * 2, 30000);
};
this.#ws.onopen = () => {
this.#reconnectDelay = 1000;
.();
};
}
() {
.#ws?.(.({ type, payload }));
}
}
class EventStore {
#events = [];
#subscribers = new Map();
append(event) {
const stored = {
...event,
id: crypto.randomUUID(),
timestamp: Date.now(),
version: this.#events.length + 1
};
this.#events.push(stored);
this.#notify(stored);
return stored;
}
replay(fromVersion = 0) {
return this.#events.filter(e => e.version >= fromVersion);
}
project(reducer, initialState) {
return this.#events.reduce(reducer, initialState);
}
subscribe(eventType, handler) {
if (!this.#subscribers.has(eventType)) this.#subscribers.set(eventType, new Set());
this.#subscribers.get(eventType).add(handler);
.#subscribers.(eventType).(handler);
}
#() {
.#subscribers.(event.)?.( (event));
.#subscribers.()?.( (event));
}
}
{ passive: true } 告诉浏览器监听器不会调用 preventDefault(),因此浏览器无需等待监听器执行完就可以开始滚动合成。Chrome 默认将 touchstart/touchmove 的 passive 设为 true。
ES2022+ 中 EventTarget 是一个可实例化的类,可以被继承和独立使用。这使得自定义元素和 Node.js 的 EventTarget 可以用于非 DOM 场景。
使用 AbortController 批量管理、WeakRef 持有回调、组件卸载时清理。addEventListener 的第三个参数支持 { signal: controller.signal } 实现自动清理。
| 模式 | 耦合度 | 适用场景 | 代表实现 |
|---|---|---|---|
| 观察者 | 高(直接引用) | 组件内通信 | MutationObserver |
| 发布订阅 | 低(中介者) | 跨模块通信 | EventBus |
| 事件流 | 低(管道) | 复杂异步逻辑 | RxJS |
| 事件委托 | — | DOM 事件优化 | jQuery.on |