Issue
Currently calling addEventListener and removeEventListener is significantly slower than native version. This has a negative impact on Angular framework performance.
Root Cause
Given
function userCallback() {....}
div.addEventListener('click', userCallback);
div.removeEventListener('click', userCallback);
In order to make the zone propagate the userCallback has to be wrapped in zoneAwareUserCallback like so:
var zoneAwareUserCallback = makeZoneAware(userCallback);
div.addEventListener('click', zoneAwareUserCallback);
div.removeEventListener('click', zoneAwareUserCallback);
Notice that when the removeEventListener gets called it needs to get called with zoneAwareUserCallback rather than userCallback.
The issue is that the addEventListener has to do the translation from userCallback to zoneAwareUserCallback internally (its monkey patched). And the same translation has to happen in removeEventListener. This translation has to be memoized for this is to work.
Currently the cost of creating the delegate and book keeping is the source of the slowdown.
The current bookkeeping involves creating an array which is patched on the EventTarget instance which contains all of the userCallbacks. For each one there is a corresponding zoneAwareUserCallback. When subsequent call is made to the removeEventListener the array is first checked to see if it contains the callback, and if so we reuse its zone-aware version.
Naive Approach
You may ask why is this not sufficient.
function userCallback() {....}
userCallback.zoneAware = makeZoneAware(userCallback);
This would simply patch the zone-aware function on the function itself and it would greatly simplify the bookkeeping. The problem is that the zone-aware function is bound to a specific Zone. This means that it can't be reused for different zones.
This code would break:
function userCallback() {....}
zoneA.run(() => div.addEventListener('click', userCallback);
zoneB.run(() => div.addEventListener('click', userCallback);
The reason is that zone-aware callback is bound to zone, hence it would restore the wrong zone.
Suggested fix: Modification of Naive Fix.
Since the callback is zone specific, instead of patching to a well known name, we could patch to a name which is zone specific. (This assumes that each zone would have a private unique symbol)
function userCallback() {....}
zoneA.run(() => {
userCallback[Zone.current.__private_symbol]= makeZoneAware(userCallback);
});
zoneB.run(() => {
userCallback[Zone.current.__private_symbol]= makeZoneAware(userCallback);
});
This would greatly simplify the book-keeping and memory pressure which is the source of the performance slowdown.
Internally addEventListener/removeEventListener can easily determine how to translate from user to zone-aware callback.
Related Code
Issue
Currently calling
addEventListenerandremoveEventListeneris significantly slower than native version. This has a negative impact on Angular framework performance.Root Cause
Given
In order to make the zone propagate the
userCallbackhas to be wrapped inzoneAwareUserCallbacklike so:Notice that when the
removeEventListenergets called it needs to get called withzoneAwareUserCallbackrather thanuserCallback.The issue is that the
addEventListenerhas to do the translation fromuserCallbacktozoneAwareUserCallbackinternally (its monkey patched). And the same translation has to happen inremoveEventListener. This translation has to be memoized for this is to work.Currently the cost of creating the delegate and book keeping is the source of the slowdown.
The current bookkeeping involves creating an array which is patched on the
EventTargetinstance which contains all of theuserCallbacks. For each one there is a correspondingzoneAwareUserCallback. When subsequent call is made to theremoveEventListenerthe array is first checked to see if it contains the callback, and if so we reuse its zone-aware version.Naive Approach
You may ask why is this not sufficient.
This would simply patch the zone-aware function on the function itself and it would greatly simplify the bookkeeping. The problem is that the zone-aware function is bound to a specific Zone. This means that it can't be reused for different zones.
This code would break:
The reason is that zone-aware callback is bound to zone, hence it would restore the wrong zone.
Suggested fix: Modification of Naive Fix.
Since the callback is zone specific, instead of patching to a well known name, we could patch to a name which is zone specific. (This assumes that each zone would have a private unique symbol)
This would greatly simplify the book-keeping and memory pressure which is the source of the performance slowdown.
Internally
addEventListener/removeEventListenercan easily determine how to translate from user to zone-aware callback.Related Code