Javascript 为什么RxJS主题比多个事件侦听器更快?

Javascript 为什么RxJS主题比多个事件侦听器更快?,javascript,angular,typescript,rxjs,dom-events,Javascript,Angular,Typescript,Rxjs,Dom Events,我最近发现一个页面的性能受到了一个在其模板上多次使用的角度指令的极大阻碍。在以下代码位中找到了性能降低的原因: @HostListener('window:keydown', ['$event']) private keydown(e: KeyboardEvent) { this.doSomething(e); } 我怀疑问题可能是由于在WindowKeyDown事件上注册了多个事件侦听器引起的,因为每次在页面上重复该指令时都会注册一个新的事件侦听器。为了验证这一理论,我创建了一个带

我最近发现一个页面的性能受到了一个在其模板上多次使用的角度指令的极大阻碍。在以下代码位中找到了性能降低的原因:

@HostListener('window:keydown', ['$event'])
private keydown(e: KeyboardEvent) {
     this.doSomething(e);
}
我怀疑问题可能是由于在WindowKeyDown事件上注册了多个事件侦听器引起的,因为每次在页面上重复该指令时都会注册一个新的事件侦听器。为了验证这一理论,我创建了一个带有RxJS主题的服务来处理键盘事件:

@Injectable()
export class KeyboardService {
    constructor() {
        window.addEventListener('keydown', event => {
            this.keydownSubject.next(event);
        });
    }
}

private keydownSubject: Subject<KeyboardEvent> = new Subject<KeyboardEvent>();

get keydown(): Observable<KeyboardEvent> {
    return this.keydownSubject.asObservable();
}

这个解决方案加快了页面速度,我很难发现为什么会出现这种情况。为什么
@HostListener
或将多个事件监听器添加到窗口的keydown事件中会比对一个RxJS主题的多个订阅更有害于页面的性能?是否默认情况下angular HostListener不是被动侦听器?

您可能试图捕获多个组件上的同一事件,这将导致所有订阅方法(
私钥关闭(e:KeyboardEvent)
)在全局窗口级别添加事件侦听器后,无论按下哪个键都将执行

这是一个更好的方法,你在你的服务,虽然我很肯定,你也可以使用

public emitter: EventEmitter<any> = new EventEmitter<any>();

@HostListener('window:keydown', ['$event'])
private keydown(e: KeyboardEvent) {
     this.doSomething(e);
}

private doSomething(event: any): void {
     this.emitter.emit(event);
}
public发射器:EventEmitter=neweventemitter();
@HostListener('window:keydown',['$event']))
私钥关闭(e:键盘事件){
这是剂量(e);
}
私人剂量测定(事件:任何):无效{
this.emitter.emit(事件);
}

在您的服务中,然后有公共的
EventEmitter
Subject
,您的组件可以在其上订阅并捕获
keyDown
事件。

它与主题无关,首先,可注入服务是单例的,因此将为您的所有指令提供一个实例,其次,在单例服务中,您在构造函数中注册一个方法来处理调用主题的keydown事件,如果您在控制台中打印一条消息,您将看到将有一个调用


但是使用Hostlistener by指令将有一个通过标记注册的事件和keydown事件的多次执行。

答案在于Angular使用Zone.js。Angular使用Zone.js进行更改检测。有关Zone.js如何工作的信息,我推荐thoughtram.io文章和

最初的问题是每次击键时页面的性能。为什么这些事件不能有效地处理?问题是Angular的变化检测。Zone.js monkey使用自己的函数修补DOM事件侦听器注册。Angular利用了这一点,使每个具有侦听器的DOM事件也触发更改检测

当重复组件的多个实例在窗口上都有自己的
@HostListener
时,它们各自独立地触发Angular的更改检测。这导致Angular试图检查整个应用程序中每个组件在每个按键上的变化,以监听键盘事件。考虑到这一点,出现性能问题也就不足为奇了


KeyboardService
解决了这个问题,因为它只触发一次更改检测。只有一个侦听器,RxJS主题在单个Zone.js执行中同步地将事件传递给每个组件的订阅。

我猜,每次您在该指令的每个实例上按一个键时,使用
@HostListener
都会触发更改检测。另一方面,当您使用带有
window.addEventListener
的服务时,情况并非如此。我推测情况确实如此,但我的问题是,为什么这比对一个RxJS主题的多次订阅更有害。它们似乎是类似的过程,人们几乎可以期望这些订阅对性能有相同的影响。将值推送到
主题
是相对简单的操作(而且是可预测的)。但变化检测的一次迭代可能介于一次比较和数十万次比较之间。所以这实际上取决于应用程序的功能。我知道
Subject.next
函数很简单。我的问题是为什么订阅响应比事件侦听器快。是不是网络浏览器没有像我预期的那样优化它?或者,这些事件仅仅需要被动侦听器就可以实现相同的结果?如果是后者,如何使用angular的HostListener?页面加载很快,但对按键事件的反应很慢,对吗?看起来有人已经报告了这个问题:顺便说一句,注册很多事件也可能很慢:我曾经遇到过一个页面上数千个工具提示的问题,因为注册所有mouseenter和mouseleave事件都很慢。这并不能回答这个问题。一旦你有足够的钱,你将能够;相反我的回答解释了为什么第二个解决方案比许多事件侦听器更快,并且与所使用的服务有关,但与主题无关。我是否应该发表评论而不是回答?谢谢
public emitter: EventEmitter<any> = new EventEmitter<any>();

@HostListener('window:keydown', ['$event'])
private keydown(e: KeyboardEvent) {
     this.doSomething(e);
}

private doSomething(event: any): void {
     this.emitter.emit(event);
}