我最近发现,页面的性能受到在其模板上多次使用的角度指令的极大阻碍。在以下代码中找到了性能降低的原因:
@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不是被动侦听器吗?
答案在于Angular对Zone.js的使用。 Angular使用Zone.js进行更改检测。有关Zone.js如何工作的信息,我建议使用thoughtram.io文章,Understanding Zones和Zones in Angular。
最初的问题是在每次击键时页面的性能中发现的。为什么事件无法有效处理?问题是Angular的变化检测。 Zone.js使用自己的函数修补DOM事件侦听器注册。 Angular利用这一点,使每个带有侦听器的DOM事件也触发更改检测。
当重复组件的多个实例在窗口上各自具有自己的@HostListener
时,它们各自独立地触发Angular的变化检测。这导致Angular尝试检查整个应用程序,以便在每个组件中检查键盘事件的每个键。考虑到存在性能问题,这也就不足为奇了。
KeyboardService
解决了这个问题,因为它只会触发一次变化检测。只有一个侦听器,RxJS Subject同步将事件传递给单个Zone.js执行中的每个组件的订阅。
它与主题无关,首先,注入服务是单例,因此将为您的所有指令提供一个实例,其次在单例服务中,您在构造函数中注册单个方法来处理调用主题的keydown事件,如果您在控制台中打印一条消息,您将看到将有一个电话。
但是使用Hostlistener by指令会有一个由标签注册的事件和在keydown事件上多次执行。
你可能试图在多个组件上捕获相同的事件,这将导致所有的订阅方法(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);
}
在您的服务中,然后有公共EventEmitter
或Subject
,您的组件可以订阅并捕获keyDown
事件。