RxJS,为什么fromEvent()为每个订阅注册一个新事件?

Sac*_*hin 5 javascript rxjs

假设我们有以下代码

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!');
});
Run Code Online (Sandbox Code Playgroud)

当我查看调试器中注册的事件时,我发现文档上注册了两个单击事件。当 的订阅数量增加时clickEvent$,注册到文档的事件数量也会随着每次订阅而增加。

调试器截图

相比之下,无论我向switch语句添加多少个案例,下面的代码都只注册一个事件。

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

所以我的问题是:-

  • 为什么每个对 clickEvent$(或任何 DOM 事件流)的订阅都会向文档添加一个新事件。
  • 当有 100 个或更多订阅时,它如何影响性能
  • 有没有办法解决这个问题,只将一个事件侦听器添加到文档中。

这是我对热和冷可观察量的了解

  • 默认情况下,从 DOM 事件创建的所有可观察序列都是热门且共享的。
  • 对于 Hot observable,订阅者仅从订阅时开始接收事件。而在冷可观察的情况下,订阅者会收到可观察可以产生的所有事件。
  • share()运算符可用于使冷可观察到热。

谢谢!

Hit*_*nds 6

正如您通过单击可以看到的run code snippet答案是肯定的,每次您subscribe访问click$流时都会附加听众。

每次订阅生成的 Observable 时,事件处理函数都会注册到给定事件类型的事件目标。当该事件触发时,作为第一个参数传递给注册函数的值将由输出 Observable 发出。当 Observable 取消订阅时,函数将从事件目标中取消注册。

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);
Run Code Online (Sandbox Code Playgroud)
<script src="https://cdnjs.cloudflare.com/ajax/libs/rxjs/6.4.0/rxjs.umd.js"></script>

<button id="test">click here</button>
Run Code Online (Sandbox Code Playgroud)

您可以创建shared流来避免这种情况。

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);
Run Code Online (Sandbox Code Playgroud)
<script src="https://cdnjs.cloudflare.com/ajax/libs/rxjs/6.4.0/rxjs.umd.js"></script>

<button id="test">click here</button>
Run Code Online (Sandbox Code Playgroud)

正如您在最后一个示例中看到的,该日志registering listener仅发生一次。有关更多信息the share operator

希望能帮助到你!