It's nice that JavaScript will do enough tree-shaking that it will eliminate the single-closure case, but that's an optimization, not something you should be counting on.
Similarly, I bet if you had a call to "eval" in there, you'd keep the entire frame, but I don't understand the exact semantics of JavaScript's eval enough to really say.
I suppose this boils down to whether your mental model is more "frame-like" or more "upvalue-like". If you think of keeping around the containing frame, it's not at all surprising that you retain the variable. If you think of the closure as a discrete set of upvalues, then it's a surprise.
Why would that be a reasonable assumption? If a variable cannot ever be referenced, it should be garbage collected. In the example, "str" cannot ever be referenced. This is why we use garbage collected languages.
> but that's an optimization, not something you should be counting on.
It's not an optimization, it's correct GC behavior, and it's absolutely something you should be counting on if you're using a GC language.
I think I've used scheme implementations that have the same "feature", with less of an excuse.
It's a similar "failure of GC" if you create an object with two fields, like "obj={a: giant_value, b: 5}" then had your closure return obj.b. I bet the "a" slot would not be GC'd, even though there's no way to reach it.
If your mental model is that you're holding onto the lexical frame, it's a pretty much the same behavior.
It absolutely should be GC'd. To demonstrate (on node again), this uses about 10MB of RSS, because obj.a cannot be GC'd.
j = function() {
var obj = {
a: new Array(1000000).join('*'),
b: 5
};
return obj;
}();
setTimeout(function() {}, 30000);
If you change "return obj" to "return obj.b", RSS is about 9kb.Edit: I think your understanding of GC might be coming from R's generational GC, which is a heuristic and quite different from V8's incremental GC: http://blog.chromium.org/2011/11/game-changer-for-interactiv...
Generational can be actually viewed as a variation of incremental GC.
function frame() {
var str = "blah";
return function() {
eval("console.log(str)");
}
}
Edit: updated to use eval instead of setTimeout because setTimeout(string) do not share the scope.