Skip to content
This repository was archived by the owner on Feb 26, 2024. It is now read-only.
This repository was archived by the owner on Feb 26, 2024. It is now read-only.

Slow addEventListener performance. #798

@mhevery

Description

@mhevery

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

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions