Search code examples
javascriptoopdommemory-leaksremovechild

javascript memory leak when DOM element deleted, what kind of reference is affected only global var or class property too?


I know this kind of stuff can cause memory leak because the child element has a reference in global scope :

let parent = document.getElementById("parent");
let child = document.getElementById("child");
parent.addEventListener("click", function(){
    child.remove();
});

To avoid this, child should be in local scope (in a function). But if I do :

class foo() {
parent = document.getElementById("parent");
child = document.getElementById("child");
remove() {
var that = this;
this.parent.addEventListener("click", function(){
    that.child.remove();
});
}
let Foo = new foo;
Foo.remove();

Will I run in the same problem or not? It is unclear. For me, It should not because class has it's own scope but I would like a confirmation please!


Solution

  • To avoid this, child should be in local scope (in a function)

    That's not quite enough.

    (function() {
        let parent = document.getElementById("parent");
        let child = document.getElementById("child");
        parent.addEventListener("click", function(){
            child.remove();
        });
    }());
    

    will still leak the memory since the document holds onto the #parent element which holds onto the click event listener which is a closure over the child variable which holds onto the #child element. To fix this, you'll need to write

    (function() {
        let parent = document.getElementById("parent");
        parent.addEventListener("click", function(){
            let child = document.getElementById("child");
            if (child) child.remove();
        });
    }());
    

    or

    (function() {
        let parent = document.getElementById("parent");
        let child = document.getElementById("child");
        parent.addEventListener("click", function(){
            child.remove();
        }, {once: true}); // automatically removeAddEventListener after first event
    }());
    

    But if I do [use a class] will I run in the same problem or not? It should not because class has its own scope.

    Yes, same problem as with the global variable even. The Foo variable to store your instance is global, and Foo.child is still a globally accessible reference. But even you didn't store the instance in a global variable, you'd still leak the reference to the #child element through the event listener on the #parent element, just like in my first snippet.

    And no, classes don't have their own scope. Methods do have, but the properties (class fields in your example) are not "scoped" at all, they're just properties of objects. Their references can be followed much easier than that of variables in closures. You don't even need the class to analyse this, your code is exactly equivalent to

    let Foo = {
        parent: document.getElementById("parent"),
        child: document.getElementById("child"),
    };
    (function remove() {
        var that = Foo;
        Foo.parent.addEventListener("click", function(){
            that.child.remove();
        });
    }());