Garbage Collection for Event Listeners

In order to avoid memory leaks and prevent subtle bugs, we typically need to remove event listeners when the respective DOM element disappears. Except the browser already takes care of this for descendants.

This is best exemplified with a custom element:

customElements.define("dummy-dialog", class extends HTMLElement {
    connectedCallback() {
        // …

        this.addEventListener("pointerenter", this);
        this.addEventListener("pointerleave", this);
        this.addEventListener("input", this);

        this._container = document.body;
        this._container.addEventListener("click", this);
    }

    disconnectedCallback() {
        this._container.removeEventListener("click", this);
    }

    handleEvent(ev) {
        // …
    }
});

connectedCallback sets up event handlers to respond to

Yet disconnectedCallback only needs to de-register the last event handler there. That’s because when an element is removed from the DOM2, the browser automatically scraps event listeners for that element and its children, so we don’t have to take care of those ourselves: In the demo above, the console’s instrumentation shows that none of the listeners are invoked after discarding our element.

The browser can’t reasonably do the same for elements beyond those immediately affected by the removal; it doesn’t know about the connection between our custom element and its parent container.