Do variables need to be referenced for them to be included in a closure?
When creating a closure (in Javascript or C#), are all variables in the scope at the time of the closure's creation "enclosed" in it? Or just the variables that are referenced in the newly created method?
Example C# code:
private void Main() {
var referenced = 1;
var notReferenced = 2; // Will this be enclosed?
new int[1].Select(x => referenced);
}
Example Javascript code:
var referenced = 1;
var notReferenced = 2; // Will this be enclosed?
var func = function () {
alert(referenced);
}
(Question came to me when reading about memory leaks in IE by creating circular references with Javascript closures. http://jibbering.com/faq/notes/closures/#clMem)
Note: with the word "enclosed", I mean what MSDN would call "captured". (http://msdn.micros开发者_如何学运维oft.com/en-us/library/bb397687.aspx)You have two questions here. In the future you might consider posting two separate questions when you have two questions.
When creating a closure in Javascript, are all variables in the scope at the time of the closure's creation "enclosed" in it?
You do not state which of many versions of "JavaScript" you are talking about. I'm going to assume you are talking about a correct implementation of the ECMAScript 3 language. If you are talking about some other version of "JavaScript", say which one you're talking about.
ECMAScript 5 has updated rules on how lexical environments and "eval" work. I have not been a member of Technical Committee 39 since 2001, and I have not been keeping up-to-date with recent changes to the ECMAScript 5 spec; if you want an answer in the context of recent ECMAScript 5 rules, find an expert on that specification; I'm not it.
The answer to your question in the context of ECMAScript 3 is yes. The ECMAScript 3 specification is very clear on this point, but you don't need to look at the spec to know that this must be the case:
function f()
{
var referenced = 1;
var notReferenced = 2;
var func = function ()
{
alert(referenced);
alert(eval("notReferenced"));
}
return func;
}
f()();
How can "eval" work correctly if "notReferenced" is not captured?
This is all explained in the ECMAScript specification, but I can briefly sum it up here.
Every variable is associated with a "variable object", which is the object that has properties whose names are the names of the variables. The variable object for the function f is identical to the activation object of f -- that is, the object that is magically created every time f is invoked. The variable object has three properties: "referenced", "notReferenced" and "func".
There is a variable object called the "global object" which represents the code outside of any function. It has a property "f".
Every execution context has a scope chain, which is the list of objects whose properties are searched when trying to evaluate an identifier.
Every function object has a scope chain associated with it, which is a copy of the scope chain that was in effect when the function was created.
The scope chain associated with "f" is the global object.
When execution enters "f", the current scope chain of the execution context has the activation object of "f" pushed onto it.
When "f" creates the function object assigned to "func", its associated scope chain is a copy of the current scope chain of the execution context -- that is, the scope chain that contains the activation of "f", and the global object.
OK, so now all the objects are set up correctly. f returns func, which is then invoked. That creates an activation object for that function. The scope chain of the execution context is fetched from the function object -- remember, it is the activation object of "f" plus the global object -- and onto that copy of the scope chain we push the current activation object. Since the anonymous function we are now executing has neither arguments nor locals, this is essentially a no-op.
We then attempt to evaluate "alert"; we look up the scope chain. The current activation and the "f" activation do not have anything called "alert", so we ask the global object, and it says yes, alert is a function. We then evaluate "referenced". It's not on the current activation object, but it is on f's activation object, so we fetch its value and pass it to alert.
Same thing on the next line. The global object tells us that there are methods "alert" and "eval". But of course "eval" is special. Eval gets a copy of the current scope chain as well as the string argument. Eval parses the string as a program and then executes the program using the current scope chain. That program looks up "notReferenced" and finds it because it is on the current scope chain.
If you have more questions about this area then I encourage you to read chapters 10 and 13 of the ECMAScript 3 specification until you thoroughly understand them.
Let's take a deeper look at your question:
When creating a closure in Javascript, are all variables in the scope at the time of the closure's creation "enclosed" in it?
To definitively answer that question you need to tell us precisely what you mean by "the closure" -- the ECMAScript 3 specification nowhere defines that term. By "the closure" do you mean the function object or the scope chain captured by the function object?
Remember, all these objects are mutable -- the scope chain itself is not mutable, but every object in the chain is mutable! Whether or not there was a variable "at the time" of the creation of either the scope chain or the function object is actually a bit irrelevant; variables come, variables go.
For example, variables that have not yet been created at the time of the function object's creation can be captured!
function f()
{
var func = function ()
{
alert(newlyCreated);
}
eval("var newlyCreated = 123;");
return func;
}
f()();
Clearly "newlyCreated" is not a variable at the time the function object is created because it is created after the function object. But when the function object is invoked, there it is, on f's activation/variable object.
Similarly, a variable that exists at the time a nested function object is created can be deleted by the time the function object is executed.
You have two questions here. In the future you might consider posting two separate questions when you have two questions.
When creating a closure in C#, are all variables in the scope at the time of the closure's creation "enclosed" in it? Or just the variables that are referenced in the newly created method?
It depends on whether you're asking for a de jure or a de facto answer.
First off, let's clarify your question. We define an "outer variable" as a local variable, value parameter (that is, not ref or out), "params" parameter, or "this" of an instance method that occurs inside a method but outside of any lambda or anonymous method expression that is inside that method. (Yes, "this" is classifed as an "outer variable" even though it is not classified as a "variable". That's unfortunate, but I've learned to live with it.)
An outer variable that is used inside of a lambda or anonymous method expression is called a "captured outer variable".
So now let's reformulate the question:
When creating the delegate at runtime that corresponds to a lambda or anonymous method, are the lifetimes of all the outer variables that are in scope at the time of the delegate's creation extended to match (or exceed) the lifetime of the delegate? Or are only the lifetimes of captured outer variables extended?
De facto, only the lifetimes of captured outer variables are extended. De jure, the spec requires the lifetimes of captured outer variables to be extended, but does not forbid the lifetimes of uncaptured outer variables to be extended.
Read sections 5.1.7, 6.5.1 and 7.15.5 of the C# 4 specification for all the details.
As Jon notes, we do not do a particularly good job of ensuring that lifetimes of captured outer variables are minimized. We hope to someday do better.
I can only answer on the C# side.
If the variable isn't actually captured by the closure, it will stay as a normal local variable in the method. It will only be hoisted to be an instance variable in a synthetic class if it's captured. (I'm assuming that's what you're talking about when you talk about variables being "enclosed".)
Note, however, that if two lambda expressions each capture different variables of the same scope, then in the current implementation both lambdas will use the same synthetic class:
private Action Foo() {
var expensive = ...;
var cheap = ...;
Action temp = () => Console.WriteLine(expensive);
return () => Console.WriteLine(cheap);
}
Here the returned action would still keep the "expensive" reference alive. All of this is an implementation detail of the Microsoft C# 4 compiler, however, and may change in the future.
I only can answer about C#:
No, it will not be enclosed, because it is not needed.
I interpret "will it be enclosed?" in this context like the following:
Will it be captured inside the closure that is generated for the lambda expression, i.e. will there be a field in the automatically generated class that contains the value of this variable?
Disclaimer:
This is an implementation detail. My answer is true for the current implementation of the C# compiler by MS for your specific case. It could be different with the Mono compiler or a newer version of the MS compiler. Or - as Jon's answer shows - it could be even different for a more complex example.
When creating a closure (in Javascript or C#), are all variables in the scope at the time of the closure's creation "enclosed" in it? Or just the variables that are referenced in the newly created method?
I can only answer about JavaScript.
This is implementation specific. Some engines enclose all variables. Some engines optimise and only enclose referenced variables.
I know Chrome optimises and only encloses the variables it cares about.
Further reading:
- Closures and scope
- V8 Closures implementation
It also seems the question originated from a concern about the old IE memory leak problem. This isn't an issue in modern browsers.
A good read on memory leaks that still exist in IE8 by making circular references between EcmaScript and Host objects :
IE8 memory leak
To clarify about the IE memory leak issues with closures, it's mainly about the circular references between Host objects (DOM) and JavaScript. So this issue does not exist unless you use the DOM (el.attachEvent
or something similar)
精彩评论