V8 stores all variables referenced from nested functions (x and y in
your case) on the heap. That way an update to a closed-over variable
from one nested scope will be visible in another. (Note that this is
only strictly necessary for variables which are ever mutated; variables
which are never set may simply be copied.) So closing over a variable
does currently have a small cost, in that it must be accessed through a
double indirection: once to get the pointer to the context object, and
another to dereference the variable.
See scope.h and scope.cc for a fairly well-commented analysis code. At
runtime the values are stored in an array in a "context" object; see
contexts.{h,cc}.
Note that Crankshaft can at times access the value of variables stored
in heap slots directly (in registers, for example).
> Partially, I'm just curious, and partially I'm wondering about
> performance implications. If f defined way more variables, is each
> closure going to chew up more memory to keep track of all of those
> pointers?
The cost is only paid for variables referenced by nested functions. The
cost also appears to be only once per scope, and not per-function; an
odd decision, and one that really falls more on the side of "objects are
closures" than "closures are objects" ;-)
http://people.csail.mit.edu/gregs/ll1-discuss-archive-html/msg03277.html
> Also, from a GC perspective, I'm wondering in the case that something
> like this happens:
> function g() {
> var x = 0
> var y = 1;
> return function() {
> return x;
> };
> }
>
> // lets day h is a global that sticks arround forever.
> var h = g();
>
> Will y never be garbage collected since its part of an execution
> context pointed to by the closure held in h?
Y was never allocated on the heap in this case.
Happy hacking,
Andy