close

Filter

loading table of contents...

Studio Developer Manual / Version 2404

Table Of Contents

8.3.3.2 Memory Leaks Caused by Non-Detached Listeners

Always remove any listeners that you attach to an Observable, Bean, ValueExpression, or any other object that emits events. Even when using the option {single: true}, the event might not have been fired at all when your component is destroyed.

A typical error pattern is to attach some method handleFoo as event listener, but by mistake hand in another method with a similar name handleFuu when intending to remove the listener. No error whatsoever is reported, because trying to remove a function as listener that is not in the current set of listeners is silently ignored by Observable#removeListener() and all other event emitters.

A useful utility to automate removing listeners is to use Observable#mon() instead of Observable#on() (alias: Observable#addListener()). mon does not attach the listener to the caller, but to the first parameter, but binds it to the lifetime of the caller. For example, when your custom component creates a DOM element elem and registers a click listener like so: this.mon(elem, "click", handleClick), the listener is automatically detached when your component (the caller, this) is destroyed.

Caution

Caution

It never makes sense to call comp.mon(comp, ...), because when a component is destroyed, it removes its own listeners, anyway. Using comp.mon(comp, "destroy", handleDestroy) even leads to the handler never being called, because a component removes all mon listeners already in its beforedestroy phase. In contrast, comp.on("destroy", handleDestroy) works as expected.

Not only components, but any objects that register event handlers, most prominently actions, have to detach all event handlers again.

As actions do not have a destroy event and onDestroy method like components, you have to override addComponent() and removeComponent() to detect when an action starts and ends being used by any component. Introducing a simple counter field starting with zero, you should acquire resources (for example, register event listeners, populate fields) when addComponent() is called while the counter is zero before increasing, and release resources (remove event listeners, set fields to null) when removeComponent() is called while the counter is zero after decreasing.

To minimize the impact in case event listeners are not detached, and to avoid cyclic dependencies, keep the scope of any event handler function or method as small as possible. In the optimal case, the event handler function is a private static method, for example if it just toggles a style class of the DOM element given in the event object:

  #attachListeners():void {
    const el = this.getEl();
    // bad style: using an anonymous function that
    // does not need its outer scope at all:
    el.addListener("mouseover", e => e.getTarget().addClass("my-hover"));
    // good style: for such cases, use a static method:
    el.addListener("mouseout", this.#removeHoverCls);
  }

  static #removeHoverCls(e:IEventObject):void {
    e.getTarget().removeClass("my-hover");
  });

If your event handler only needs access to this (this current component instance), declare it as a method as opposed to an anonymous function:

  private #hoverCounter:int = 0;

  #attachListeners():void {
    const el = getEl();
    // bad style: using an anonymous function that
    // only needs to access "this":
    el.addListener("mouseover", e => ++this.++hoverCounter);
    // good style: for such cases, use a (non-static) method:
    el.addListener("mouseout", bind(this, this.#countHoverEvent));
  }

  #countHoverEvent(e:IEventObject):void {
    ++this.hoverCounter;
  });

In TypeScript, like in JavaScript, anonymous or inline functions have lexical scope, that is they can access any variable declared in the surrounding function or method. Since this scope usually contains a reference to the object that emits events (here: el), and that object stores your event handler function in its listener set, you create a cyclic reference between the two. Cyclic references are not bad per se, because garbage collection can handle them if all objects contained in the cycle are not referenced from "outside". But firstly, as long as any of the objects is kept alive, all others are retained, too, and secondly, as discussed below, this makes finding the real culprit for memory leaks harder.

Search Results

Table Of Contents
warning

Your Internet Explorer is no longer supported.

Please use Mozilla Firefox, Google Chrome, or Microsoft Edge.