1. 8
  1. 4

    Years ago, I worked a lot with streaming data into the browser for applications that needed to work for a week or more continuously open in a browser window. We noticed that IE had a memory leak simply from inserting keys and deleting keys in objects. We noticed it in process memory usage on long running tests, but there was an interesting way you could observe it from inside the language.

    var x = {a: 1, b:2};

    delete x.a;

    x.a = 9;

    Object.keys(x); // back then you had to loop over the keys, but now you can just call Object.keys :-)

    In IE, it returns [“a”, “b”], but the correct answer (as returned by firefox and Chrome) is [“b”, “a”]. The fact that IE returns what it does shows that it ‘remembered’ the fact that the object previously held “a”, even though it had been deleted.

    We ended up needing to keep track of the objects that had the most churn and throw them away and replace them with entirely new ones periodically.

    1. 2

      This is a good overview.

      WeakMaps (and the other Weak* objects) in particular have been interesting to try to wrap my head around.

      From my experience, events have been the #1 source of leaky behavior, usually related to:

      1. Adding the same listener over and over again: addEventListener will not add duplicate listeners when given the same reference. It will, however, add a gazillion instances of an anonymous function, or a function bound in an ad-hoc manner with bind(); e.g. el.addEventListener('click', this.myfunc.bind(this)).
      2. Failing to remove the listener you think you’re removing, due to one of:
        • a typo, e.g. el.removeEventListener('click', this.myfuncc)removeEventListener() will complain if you forget the second argument, but will not flinch when you accidentally send an undefined reference to it;
        • a mismatch between the event phases — capture and bubble are different “slots” so addEventListener('click', fn, true) will not be removed by removeEventListener('click', fn).

      One of the reasons why I’d occasionally fire up Chrome (I use Firefox for work and leisure) was better profiling. It used to be the case that I’d open up Performance Monitor (or whatever it was called back then) and look at how the number of event listeners and DOM nodes fluctuates in real-time. Dev tools in all browsers change quite often, and I haven’t caught up, but as far as I can tell, Performance Monitor is still there [1], but it’s hampered by:

      • Heavy-handed DOM libraries (such as React) liberally creating DOM nodes
      • The browser taking its sweet sweet time to garbage collect the objects

      I find that makes watching the DOM node / event listener count much less useful (unless I’m massively misinterpreting things).

      [1]: Note that Performance Monitor is a different tool than Performance. It shows stats in real-time, as opposed to on-demand recording. You can find it in ⋮ > More Tools… and is one of those tabs that open next to Console.

      1. 1

        Luckily, Chrome still supports the getEventListeners(element) method, and with not too much effort you can build your own system to monitor event listeners on the DOM tree (not too mention dev tools in all browsers have gained alternative ways to inspect listeners).

        I’ve also remembered an article by @nolan that was submitted recently: https://lobste.rs/s/nezhx0/fixing_memory_leaks_web_applications — currently bookmarked for future reference (when I finally decide to brush up my dusty dev tool skills), but a quick search for garbage seems to indicate this is an annoyance in other tools (heap snapshots) as well…