除事件监听模式外,JavaScript不同对象间通信是否有更优实现方案?
Great job getting started with event-driven communication between objects—your current implementation nails the core idea of connecting emitters and listeners, but it has a critical limitation: each event can only have one listener attached at a time (since you’re overwriting the event1/event2 properties directly). Let’s walk through some more robust, flexible alternatives that fix this and scale better for real-world use:
1. Improved Observer Pattern (Supports Multiple Listeners)
This is a direct upgrade to your existing code that lets multiple listeners subscribe to the same event—just like standard DOM event handling. Instead of storing a single function per event, we use arrays to hold all active listeners.
class Emitter { constructor() { // Store listeners in an object of arrays, one per event this.events = { event1: [], event2: [] }; } someEmitterLogic(input) { // Trigger ALL listeners for event1 this.events.event1.forEach(listener => listener({message:"produced at event1", b:1})); // Trigger ALL listeners for event2 this.events.event2.forEach(listener => listener({message:"produced at event2", d:2})); } addEvent(event, listener) { if (this.events.hasOwnProperty(event)) { // Optional: Prevent duplicate listener registrations if (!this.events[event].includes(listener)) { this.events[event].push(listener); } } else { console.log("Sorry, we don't emit this event."); } } removeEvent(event, listener) { if (this.events.hasOwnProperty(event)) { // Filter out the specific listener to remove this.events[event] = this.events[event].filter(fn => fn !== listener); } else { console.log("Sorry, cannot remove an event we are not emitting."); } } // Optional: Clear all listeners for an event removeAllListeners(event) { if (this.events.hasOwnProperty(event)) { this.events[event] = []; } } } // Usage stays mostly the same, but now you can add multiple listeners! const emitter = new Emitter(); const listener = new Listener(); emitter.addEvent("event1", listener.listener1); emitter.addEvent("event1", (e) => console.log(`Second listener got: ${e.message}`)); // Works now!
Why this works better:
- Supports multiple listeners per event without overwriting existing ones
- Lets you remove specific listeners (not just reset the entire event)
- Aligns with familiar event system behavior (like
addEventListenerin the browser)
2. Publish/Subscribe (Pub/Sub) Pattern
If you want full decoupling between emitters and listeners (they don’t need to know about each other at all), use a Pub/Sub "message broker" as a middleman. Emitters publish events to the broker, and listeners subscribe to events from the broker.
// Central Pub/Sub broker class PubSub { constructor() { this.topics = {}; } // Subscribe to a topic, returns a function to unsubscribe later subscribe(topic, listener) { if (!this.topics[topic]) { this.topics[topic] = []; } this.topics[topic].push(listener); return () => { this.topics[topic] = this.topics[topic].filter(fn => fn !== listener); }; } // Publish data to a topic publish(topic, data) { if (this.topics[topic]) { this.topics[topic].forEach(listener => listener(data)); } } } // Emitter now only interacts with the PubSub broker class Emitter { constructor(pubSub) { this.pubSub = pubSub; } someEmitterLogic(input) { this.pubSub.publish("event1", {message:"produced at event1", b:1}); this.pubSub.publish("event2", {message:"produced at event2", d:2}); } } // Listener also only interacts with the PubSub broker class Listener{ constructor(pubSub){ // Store unsubscribe functions for easy cleanup this.unsubscribeEvent1 = pubSub.subscribe("event1", (e) => console.log(e.message)); this.unsubscribeEvent2 = pubSub.subscribe("event2", (e) => console.log(e.message)); } stopListeningToEvent1() { this.unsubscribeEvent1(); } } // Usage const pubSub = new PubSub(); const emitter = new Emitter(pubSub); const listener = new Listener(pubSub); emitter.someEmitterLogic(); // Both listeners fire listener.stopListeningToEvent1(); emitter.someEmitterLogic(); // Only event2 listener fires
Why this works better:
- Complete decoupling: Emitters and listeners don’t reference each other directly
- Scales easily to multiple emitters and listeners
- Clean unsubscribe flow with returned functions
3. ES6 Proxy (For Property-Based Notifications)
If your use case involves reacting to changes in an object’s properties (instead of explicit event triggers), an ES6 Proxy can automatically notify listeners when properties are updated. This is great for state-tracking scenarios.
class Emitter { constructor() { this.data = { event1Data: null, event2Data: null }; this.event1Listeners = []; this.event2Listeners = []; // Create a Proxy to intercept property changes this.proxiedData = new Proxy(this.data, { set: (target, prop, value) => { target[prop] = value; // Trigger listeners when specific properties change if (prop === "event1Data") { this.event1Listeners.forEach(listener => listener(value)); } else if (prop === "event2Data") { this.event2Listeners.forEach(listener => listener(value)); } return true; } }); } someEmitterLogic(input) { // Updating the proxied property triggers notifications this.proxiedData.event1Data = {message:"produced at event1", b:1}; this.proxiedData.event2Data = {message:"produced at event2", d:2}; } addEvent(event, listener) { if (event === "event1") { this.event1Listeners.push(listener); } else if (event === "event2") { this.event2Listeners.push(listener); } else { console.log("Sorry, we don't emit this event."); } } }
Why this works better:
- Automates notifications when properties change (no manual event triggers)
- Clean for state-driven applications
Which to Choose?
- Use the improved observer pattern if you want to upgrade your existing code with minimal changes and support multiple listeners.
- Use Pub/Sub if you need full decoupling between emitters and listeners (great for large apps with many components).
- Use Proxy if your communication is tied to object property changes instead of explicit events.
内容的提问来源于stack exchange,提问作者user 4234-483244-34

