I'm aware of the great posts on Closures here and here, but neither seems to address the particular case I have in mind. The question is best demonstrated with code:
function foo() {
var x = {};
var y = "whatever";
return function bar() {
alert(y);
};
}
var z = foo();
Referencing y
within bar
invokes a closure, and so long as I keep z
around the garbage collector won't clean up y
. The question is -- what happens to x
? Is it held by that closure too even though it doesn't get referenced? Will the garbage collector see there's no reference x
and clean it up? Or will x
persists along with y
as long as I hold onto z
? (An ideal answer would cite the ECMA Specification.)
Global variables live until the page is discarded, like when you navigate to another page or close the window. Local variables have short lives. They are created when the function is invoked, and deleted when the function is finished.
3How would you use a closure to create a private counter? You can create a function within an outer function (a closure) that allows you to update a private variable but the variable wouldn't be accessible from outside the function without the use of a helper function.
It is also not possible to programmatically trigger garbage collection in JavaScript — and will likely never be within the core language, although engines may expose APIs behind opt-in flags.
Closures are frequently used in JavaScript for object data privacy, in event handlers and callback functions, and in partial applications, currying, and other functional programming patterns.
The question is -- what happens to x?
The answer varies depending on theory vs. implementation.
In theory, yes, x
is kept alive, because the closure (the anonymous function) has a reference to the binding object of the context of the call to foo
, which includes x
.
In practice, modern JavaScript engines are quite smart. If they can prove to themselves that x
cannot be referenced from the closure, they can leave it out. The degree to which they do that will vary from engine to engine. Example: V8 (the engine in Chrome and elsewhere) will start out with x
, y
, and even the object that x
refers to on the stack, not the heap; then when exiting foo
, it looks to see what things still have outstanding references, and moves those to the heap. Then it pops the stack pointer, and the other things don't exist anymore. :-)
So, how can they prove it? Basically, if the code in the closure doesn't refer to it and doesn't use eval
or new Function
, the JavaScript engine is likely to be able to know that x
isn't needed.
If you need to be sure that even if x
still exists, the object is available for GC even on older browsers that might be literal (dumb) about it, you can do this:
x = undefined;
That means nothing keeps a reference to the object x
used to refer to. So even though x
still exists, at least the object it referred to is ready for reaping. And it's harmless. But again, modern engines will optimize things for you, I wouldn't worry about it unless you were faced with a specific performance problem and tracked it down to some code allocating large objects that aren't referenced once the function returns, but don't seem to be getting cleaned up.
Unfortunately, as you pointed out below, there are limits to this, such as the one mentioned in this question. But it's not all doom and gloom, see below under the profile snapshot for what you can do...
Let's look this code in V8, using Chrome's heap snapshot feature:
function UsedFlagClass_NoFunction() {}
function UnusedFlagClass_NoFunction() {}
function build_NoFunction() {
var notused = new UnusedFlagClass_NoFunction();
var used = new UsedFlagClass_NoFunction();
return function() { return used; };
}
function UsedFlagClass_FuncDecl() {}
function UnusedFlagClass_FuncDecl() {}
function build_FuncDecl() {
var notused = new UnusedFlagClass_FuncDecl();
var used = new UsedFlagClass_FuncDecl();
function unreachable() { notused; }
return function() { return used; };
}
function UsedFlagClass_FuncExpr() {}
function UnusedFlagClass_FuncExpr() {}
function build_FuncExpr() {
var notused = new UnusedFlagClass_FuncExpr();
var used = new UsedFlagClass_FuncExpr();
var unreachable = function() { notused; };
return function() { return used; };
}
window.noFunction = build_NoFunction();
window.funcDecl = build_FuncDecl();
window.funcExpr = build_FuncExpr();
And here's the expanded heap snapshot:
When processing the build_NoFunction
function, V8 successfully identifies that the object referenced from notused
cannot be reached and gets rid of it, but it doesn't do so in either of the other scenarios, despite the fact that unreachable
cannot be reached, and therefore notused
cannot be reached through it.
So what can we do to avoid this kind of unnecessary memory consumption?
Well, for anything that can be handled via static analysis, we can throw a JavaScript-to-JavaScript compiler at it, like Google's Closure Compiler. Even in "simple" mode, the beautified result of "compiling" the code above with Closure Compiler looks like this:
function UsedFlagClass_NoFunction() {}
function UnusedFlagClass_NoFunction() {}
function build_NoFunction() {
new UnusedFlagClass_NoFunction;
var a = new UsedFlagClass_NoFunction;
return function () {
return a
}
}
function UsedFlagClass_FuncDecl() {}
function UnusedFlagClass_FuncDecl() {}
function build_FuncDecl() {
new UnusedFlagClass_FuncDecl;
var a = new UsedFlagClass_FuncDecl;
return function () {
return a
}
}
function UsedFlagClass_FuncExpr() {}
function UnusedFlagClass_FuncExpr() {}
function build_FuncExpr() {
new UnusedFlagClass_FuncExpr;
var a = new UsedFlagClass_FuncExpr;
return function () {
return a
}
}
window.noFunction = build_NoFunction();
window.funcDecl = build_FuncDecl();
window.funcExpr = build_FuncExpr();
As you can see, static analysis told CC that unreachable
was dead code, and so it removed it entirely.
But of course, you probably used unreachable
for something during the course of the function, and just don't need it after the function completes. It's not dead code, but it is code you don't need when the function ends. In that case, you have to resort to:
unused = undefined;
at the end. Since you don't need the function anymore, you might also release it:
unused = unreachable = undefined;
(Yes, you can do that, even when it was created with a function declaration.)
And no, sadly, just doing:
unreachable = undefined;
...doesn't succeed (as of this writing) in making V8 figure out that unused
can be cleaned up. :-(
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