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

问题描述 投票:15回答:3

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

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

我怀疑问题可能是由于在窗口keydown事件上注册多个事件侦听器引起的,因为每次在页面上重复该指令时都会注册一个新事件。为了测试该理论,我使用RxJS Subject创建了一个服务来处理该键盘事件:

@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,并在ngOnInit中订阅了这个服务的主题:

export class KeydownEventDirective implements OnInit, OnDestroy {
    constructor(private keyboardService: KeyboardService) {}

    private keydown(e: KeyboardEvent) {
        this.doSomething(e);
    }

    private keydownSubscription: Subscription;
    ngOnInit() {
        this.keydownSubscription =
            this.keyboardService.keydown.subscribe(e => {
                this.keydown(e);
            });
    }

    ngOnDestroy() {
        this.keydownSubscription.unsubscribe();
    }

    ...
}

解决方案加速了页面,我很难发现为什么会出现这种情况。为什么@HostListener或向窗口的keydown事件添加多个事件监听器比对RxJS主题的​​多个订阅更不利于页面的性能?可能是默认情况下角度HostListeners不是被动侦听器吗?

javascript angular typescript rxjs dom-events
3个回答
2
投票

答案在于Angular对Zone.js的使用。 Angular使用Zone.js进行更改检测。有关Zone.js如何工作的信息,我建议使用thoughtram.io文章,Understanding ZonesZones in Angular

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

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

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


1
投票

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

但是使用Hostlistener by指令会有一个由标签注册的事件和在keydown事件上多次执行。


0
投票

你可能试图在多个组件上捕获相同的事件,这将导致所有的订阅方法(private keydown(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);
}

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

© www.soinside.com 2019 - 2024. All rights reserved.