I was reading about how circular references cause memory leaks in IE, but I was pretty confused on an example using a closure within closure to break the circular reference:
function addHandler() {
var clickHandler = function() {
this.style.backgroundColor = 'red';
};
(function() {
var el = document.getElementById('el');
el.onclick = clickHandler;
})();
}
My head got all wrangled with what referenced to what, which are the closures, which are the scope objects. Can someone break it down more explicitly than MDN? Thanks.
Here are some advantages of closures: They allow you to attach variables to an execution context. Variables in closures can help you maintain a state that you can use later. They provide data encapsulation. They help remove redundant code.
A circular reference occurs if two separate objects pass references to each other. In older browsers circular references were a cause of memory leaks. With improvements in Garbage collection algorithms, which can now handle cycles and cyclic dependencies fine, this is no longer an issue.
Circular reference is a series of references where an object references itself directly or indirectly through a series of objects, resulting in a closed loop, appearing in most computer programming including JavaScript.
If you had
function addHandler() {
var clickHandler = function() {
this.style.backgroundColor = 'red';
// can access `el` here
};
var el = document.getElementById('el');
el.onclick = clickHandler;
}
then el
has a reference to clickHandler
, but clickHandler
also has a reference to el
, because it's a closure. ->
circular reference (in IE)
By introducing a new scope, you make el
local, so it it is not accessible by clickHandler
, ->
no circular reference.
function addHandler() {
var clickHandler = function() {
this.style.backgroundColor = 'red';
// cannot access `el` here
};
(function() {
// `el` is local to this immediately invoked function
var el = document.getElementById('el');
el.onclick = clickHandler;
})();
}
So, the solution to the memory leak problem is not to introduce yet another closure, it is to create a new scope, that "shields" the values from each other (in one direction at least).
The rules of thumb that I go by:
For JavaScript closures to be created, there has to be nested functions and the inner function must have access to or is refered to by (let's just say "touch" for short) a variable in the outer function. If either of these 2 criteria is not found, then there's no closure in JavaScript.
Memory leaks occur when such variable (mentioned in the above paragraph) in the outer function happens to be a DOM element.
In the Mozilla article, let's visit the first example:
function addHandler() {
var el = document.getElementById('el');
el.onclick = function() {
this.style.backgroundColor = 'red';
};
}
Clearly, there is one function nested inside another function, and the inner function is assigned to a property of a variable (el) in the outer scope, so there is a closure created. This variable el also happens to be a DOM element, hence there's memory leak as explained in the article.
In the second example that you posted,
function addHandler() {
var clickHandler = function() {
this.style.backgroundColor = 'red';
};
(function() {
var el = document.getElementById('el');
el.onclick = clickHandler;
})();
}
There's an outer function, and nested inside this outer function are 2 nested (inner) functions, but they are at the same level, meaning one is not nested inside the other. The second nested function also has access to a local variable (clickHandler) in the outer function, so there's a closure that gets created. However, there's no memory leaks because this local variable (clickHandler) in the outer function is not a DOM element. The local variable el does not contribute to memory leaks because it's local to the second nested function and not defined in the outer function addHandler(). In other words, it is local to the second nested function, it is not accessible to the first nested function, hence there's no chance for memory leaks.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With