> That has me thinking. Do the closures really consume as much additional
> memory as the entire host scope's execution context, plus it's own, or just
> the added pointers to the variables it actually closes over (i.e. uses
> within it).
According to the spec, every execution context has a "variable
object", and the vars and parameters related to that context are
properties of that object. (The "variable object" for the global
scope is the global object -- window, in our case -- but there's no
way to directly reference the ones created for each function.) This
variable object is put at the top of the scope chain in effect for
that context, which is effectively a linked list of the variable
objects of each containing context. When you create a new function
inside a context, the new function gets its own variable object linked
at the top of the containing context's scope chain. So not a copy of
the containing context, and not pointers to individual vars that
actually get used, but a pointer to the variable object and associated
scope chain.
The memory issue comes into it because the closure keeps that entire
scope chain alive where (absent any closures) it would be eligible for
cleanup when the function exited. The nice thing about bind, aside
from convenience, is that as kangax pointed out the variable object
and scope chain are quite thin. You can happily bind without worrying
(much) about what you're keeping live.
So, scenario:
function init() {
var container;
container = $('container');
hookUpHideables(container);
container.show();
function hookUpHideables(p) {
p.select('.hideable').invoke('observe', 'click', function(evt)
{
this.hide();
});
}
}
Perhaps the author was thinking: "I don't need hookUpHideables()
except when init runs, and init() only runs once, so I'll let it get
reclaimed after init() ends." hookUpHideables() doesn't do anything
with the 'container' var, nor does the anonymous closure it creates.
But they both keep 'container' alive nevertheless, indirectly via the
scope chain. It doesn't matter for hookUpHideables() since no
reference to it survives the return of init(), and so it's eligible
for cleanup when init() returns. But the anonymous closures survive,
and so the entire scope chain does, including the reference to the
host object inside 'container'. (You can break the reference to the
host object by clearing 'container' before exiting init(), but the
scope chain lives on regardless.)
Although some optimizations are presumably possible at the
implementation level, I'd have to re-read the spec to see whether they
would violate it, and in any case exec() and such make life very
difficult for the implementers in terms of figuring out what may get
referenced at runtime.
If we recast our example to use a named function for the handler:
function init() {
var container;
container = $('container');
hookUpHideables(container);
container.show();
function hookUpHideables(p) {
p.select('.hideable').invoke('observe', 'click',
hideableClick);
}
}
function hideableClick(evt) {
this.hide();
}
...no closures have outstanding references at the end of init(), and
the chain is eligible for GC.
FWIW,
--
T.J. Crowder
tj / crowder software / com
Independent Software Engineer, consulting services available
> Nth Penguin, LLChttp://
www.nthpenguin.com