loading...
Cover image for Memory leak by anonymous functions

Memory leak by anonymous functions

jeevankishore profile image Jeevan Kishore ・2 min read

With introduction of fat arrow functions ➕ the implicit bind magic, JS developers found an everlasting 💛 towards them.

Although they were eye pleasing and a delight, they bought with a variety of concerns if not implemented wisely.

One such case i found myself having numerous conversations with my peers about; is having anonymous functions to handle the events which developers find easy to use and miss the subtle ugly memory leak they cause.

P.S This article won't be dwelling to the 🐘 depths of memory leak identification and resolutions but to emphasis on the fact that taking the easy route in this case will end up hitting the hardest.


✨ Theory

An anonymous function might not be cleared by GC (garbage collection) efficiently during a mark and sweep phase as the references to it cannot be determined hence GC fails to recover back the allocated memory


✨ Lab setup

  • Production react build running on chrome
  • Run about 10k state changes on each scenario of with and without anonymous implementation to trigger re-renders

Preview
Alt Text


✨ Analysis

That being said, let's jump on to the crux and look at some stats;

Recording a snapshot of each implementation clearly depicts a memory leak with the anonymous function implementation

Snapshot without anonymous functions
Analysis without anonymous functions
🔸 fig (i)

Snapshot with anonymous functions
Analysis with anonymous functions
🔸 fig (ii)

As we compare fig (i) with fig (ii) it's clear that the memory allocation has been freed up by GC in fig (i) as opposed to that of fig(ii)

The exaggerated example intends to portrays the memory leak with the approach; which holds true for apps of multiple complexities in the real world


✨ Conclusion =>

Anonymous fat arrow functions within render methods pave way for a memory leak and ipso facto an anti-pattern


✨ Show me the code

GitHub logo Jeevan-Kishore / space-complexity

A project to demonstrate space complexity over time


If you have questions, let us know in the comments and we are looking forward for your feedback 🍻

Posted on May 30 by:

jeevankishore profile

Jeevan Kishore

@jeevankishore

Sr. Javascript Dev with an everlasting affinity towards open-source and icecream

Discussion

markdown guide
 

I like to write anonymous event handlers on occasion and my work app is all Inversion of Control - so we have a master event bus etc. We just use hooks to add the events and then it's fine as it is properly removed.

We mostly use our master event bus which has a special hook, but the generic one looks like this:

export function useEvent(emitter, pattern, handler) {
    if (!handler) {
        ;[emitter, pattern, handler] = [events, emitter, pattern]
    }
    let runner = (...params) => {
        handler(...params)
    }
    useEffect(() => {
        if (emitter) {
            if (emitter.addEventListener) {
                emitter.addEventListener(eventName(pattern), runner)
            } else {
                emitter.addListener(eventName(pattern), runner)
            }
        }
        return () => {
            if (emitter.removeEventListener) {
                emitter.removeEventListener(eventName(pattern), runner)
            } else {
                emitter.removeListener(eventName(pattern), runner)
            }
        }
    })
}

Used like this:

function SomeComponent() {
     useEvent(window, 'resize', ()=>console.log(innerWidth, innerHeight))
}
 

Why aren't anonymous event handlers destroyed when the view is destroyed? Sounds like a design point issue.

 

It would be fair to say if the event emitter is destroyed then they are unreachable and are released. If you attach to a global event handler or something that will survive a while, they aren't removed because nothing calls removeListener. It would be true of a non-anonymous listener true I guess.

I'm guessing people aren't working out how to remember the anonymous function to then call removeListener. It's one of those, the sugar is so sweet someone didn't notice the sudden weight gain ;)

 

The responsibility is likely delegated to the user to remove listeners when they feel it's time to do so.

I agree with @miketalbot on how people overlook it.

 

How is this issue different for anonymous functions declared using the function keyword?

 

@savagepixie It isn't different, as long as there are creation of objects which cannot be GC'd the issue remains the same. The article addresses the latter as people prefer to use the fat arrows widely because of binding