perf(core): use native addEventListener for faster rendering. (#18107)
Angular can make many assumptions about its event handlers. As a result the bookkeeping for native addEventListener is significantly cheaper than Zone's addEventLister which can't make such assumptions. This change bypasses the Zone's addEventListener if present and always uses the native addEventHandler. As a result registering event listeners is about 3 times faster. PR Close #18107
This commit is contained in:

committed by
Miško Hevery

parent
8bcb268140
commit
6279e50d78
@ -6,22 +6,54 @@
|
||||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
|
||||
import {Inject, Injectable} from '@angular/core';
|
||||
import {Inject, Injectable, NgZone, ɵglobal as global} from '@angular/core';
|
||||
|
||||
import {DOCUMENT} from '../dom_tokens';
|
||||
|
||||
import {EventManagerPlugin} from './event_manager';
|
||||
|
||||
/**
|
||||
* Detect if Zone is present. If it is then bypass 'addEventListener' since Angular can do much more
|
||||
* efficient bookkeeping than Zone can, because we have additional information. This speeds up
|
||||
* addEventListener by 3x.
|
||||
*/
|
||||
const Zone = global['Zone'];
|
||||
const __symbol__ = Zone && Zone['__symbol__'] || function<T>(v: T): T {
|
||||
return v;
|
||||
};
|
||||
const ADD_EVENT_LISTENER: 'addEventListener' = __symbol__('addEventListener');
|
||||
const REMOVE_EVENT_LISTENER: 'removeEventListener' = __symbol__('removeEventListener');
|
||||
|
||||
@Injectable()
|
||||
export class DomEventsPlugin extends EventManagerPlugin {
|
||||
constructor(@Inject(DOCUMENT) doc: any) { super(doc); }
|
||||
constructor(@Inject(DOCUMENT) doc: any, private ngZone: NgZone) { super(doc); }
|
||||
|
||||
// This plugin should come last in the list of plugins, because it accepts all
|
||||
// events.
|
||||
supports(eventName: string): boolean { return true; }
|
||||
|
||||
addEventListener(element: HTMLElement, eventName: string, handler: Function): Function {
|
||||
element.addEventListener(eventName, handler as any, false);
|
||||
return () => element.removeEventListener(eventName, handler as any, false);
|
||||
/**
|
||||
* This code is about to add a listener to the DOM. If Zone.js is present, than
|
||||
* `addEventListener` has been patched. The patched code adds overhead in both
|
||||
* memory and speed (3x slower) than native. For this reason if we detect that
|
||||
* Zone.js is present we bypass zone and use native addEventListener instead.
|
||||
* The result is faster registration but the zone will not be restored. We do
|
||||
* manual zone restoration in element.ts renderEventHandlerClosure method.
|
||||
*
|
||||
* NOTE: it is possible that the element is from different iframe, and so we
|
||||
* have to check before we execute the method.
|
||||
*/
|
||||
const self = this;
|
||||
let byPassZoneJS = element[ADD_EVENT_LISTENER];
|
||||
let callback: EventListener = handler as EventListener;
|
||||
if (byPassZoneJS) {
|
||||
callback = function() {
|
||||
return self.ngZone.runTask(handler as any, null, arguments as any, eventName);
|
||||
};
|
||||
}
|
||||
element[byPassZoneJS ? ADD_EVENT_LISTENER : 'addEventListener'](eventName, callback, false);
|
||||
return () => element[byPassZoneJS ? REMOVE_EVENT_LISTENER : 'removeEventListener'](
|
||||
eventName, callback as any, false);
|
||||
}
|
||||
}
|
||||
|
Reference in New Issue
Block a user