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

Bra*_*nen 15 javascript dom-events rxjs typescript angular

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

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

我怀疑问题可能是由于在窗口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();
}
Run Code Online (Sandbox Code Playgroud)

然后我删除了@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();
    }

    ...
}
Run Code Online (Sandbox Code Playgroud)

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

Bra*_*nen 5

答案在于 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 主题将事件同步传递到单个 Zone.js 执行中的每个组件的订阅。