简体   繁体   中英

RxJS, why does fromEvent() register a new event for each subscription?

Let's say we have following code

const clickEvent$ = fromEvent(document, 'click').pipe(
    pluck('target')
);

clickEvent$.pipe(
    filter(node => node.id === 'button1')
).subscribe(() => {
    console.log('Button 1 clicked!');
});

clickEvent$.pipe(
    filter(node => node.id === 'button2')
).subscribe(() => {
    console.log('Button 2 clicked!');
});

When I look at the registered events in debugger I see that there are two click events registered on the document. When in increase number of subscriptions to clickEvent$ , the number of events registered to document also increases with each subscription.

debugger screenshot

In comparison the code below only registers one event no matter how many cases I add to switch statement.

document.addEventListener('click', (event) => {
    switch (event.target.id) {
        case 'Button1':
            console.log('Button 1 clicked!');
            break;
        case 'Button2':
            console.log('Button 2 clicked!');
            break;
    }
});

So my question is :-

  • Why does each subscription to clickEvent$ (or any DOM Event Stream) add a new event to document.
  • How does it affect performance when there 100 or more subscriptions
  • Is there a way around this to have only one event listener added to documen.

Here's what I know about Hot and Cold observables

  • All observable sequences created from DOM events are Hot and shared by default.
  • In case of a Hot observable, subscribers only start receiving events from the time of subscription. Whereas in case of a cold observable subscribers receive all events that the observable can produce.
  • share() operator can be used to make a cold observable hot.

Thanks!

as you can see by clicking run code snippet , the answer is yes , listeners are being attached every time you subscribe to the click$ stream.

Every time resulting Observable is subscribed, event handler function will be registered to event target on given event type. When that event fires, value passed as a first argument to registered function will be emitted by output Observable. When Observable is unsubscribed, function will be unregistered from event target.

 const { fromEvent } = rxjs; const { mapTo } = rxjs.operators; const target = document.getElementById('test'); /* ignore, debug */ const $delegate = target.addEventListener; target.addEventListener = (...args) => { console.log('registering listener'); return $delegate.apply(target, args); }; /* // */ const click$ = fromEvent(target, 'click'); click$.pipe( mapTo('stream 1: click'), ).subscribe(console.log); click$.pipe( mapTo('stream 2: click'), ).subscribe(console.log); 
 <script src="https://cdnjs.cloudflare.com/ajax/libs/rxjs/6.4.0/rxjs.umd.js"></script> <button id="test">click here</button> 

you can create shared streams to avoid that.

 const { fromEvent } = rxjs; const { mapTo, share } = rxjs.operators; const target = document.getElementById('test'); /* ignore, debug */ const $delegate = target.addEventListener; target.addEventListener = (...args) => { console.log('registering listener'); return $delegate.apply(target, args); }; /* // */ const click$ = fromEvent(target, 'click').pipe( share(), // <= share operator ); click$.pipe( mapTo('stream 1: click'), ).subscribe(console.log); click$.pipe( mapTo('stream 2: click'), ).subscribe(console.log); 
 <script src="https://cdnjs.cloudflare.com/ajax/libs/rxjs/6.4.0/rxjs.umd.js"></script> <button id="test">click here</button> 

as you can see in this last example, the log registering listener is happening only one time. Further information about the share operator

Hope it helps!

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM