Consider the following code:
function f() {
f = eval("" + f);
console.log("Inside a call to f(), f is: \n%s", f);
}
f();
console.log("After a call to f(), f is: \n%s", f);
I expected f
to be defined at all times during execution. However, in Chrome and IE, it is undefined
when the first console.log
is invoked, and in Firefox, it is undefined
when the second console.log
is invoked.
Why is f
not always defined? Why do Chrome/IE and Firefox behave differently?
http://jsfiddle.net/G2Q2g/
Output on Firefox 26:
Inside a call to f(), f is:
function f() { f = eval("" + f); console.log("Inside a call to f(), f is: \n%s", f); }
After a call to f(), f is:
undefined
Output on Chrome 31 and IE 11:
Inside a call to f(), f is:
undefined
After a call to f(), f is:
function f() { f = eval("" + f); console.log("Inside a call to f(), f is: \n%s", f); }
First of all, let's talk about what we would 'expect'.
I would naively expect both cases to return undefined
.
Just like: eval("function foo(){}")
which returns undefined.
Just like whenever we have a function declaration - it does not return the function value but sets it.
Just like the langue specification says for strict mode.
Update: after digging more through the spec - Firefox is correct here.
Here is what Firefox is doing
Visualized:
- f
= eval("" + f);
// set the left hand side to the functionf
we're inf =
eval("" + f); // declare a new functionf
in the scope of this functionf = undefined;
// sinceundefined === eval("function(){}");
*
* since function declarations do not return anything - just like function foo(){} has no return value
Since f was decided in step 1, right now the reference to the function we're in was overwritten with undefined and a local closure declared f
was declared with the same code.
Now when we do:
console.log("Inside a call to f(), f is: \n%s",
f)
// f is the local closure variable, it's closest
Suddenly, it's obvious we get the function - it's a member variable.
However, as soon as we escape the function
console.log("After a call to f(), f is: \n%s",
f);
Here, f is undefined since we overwrote it in step 1.
Chrome and IE make the mistake of assigning it to the wrong f
and evaluating the right hand side before the left hand side of an assignment.
Note that the next section says in Entering eval code:
Let strictVarEnv be the result of calling NewDeclarativeEnvironment passing the LexicalEnvironment as the argument.
Which explains why it works in strict mode - it's all run in a new context.
Same thing, but in more text and less graphically
f
from f =
(since the left hand side must be evaluated first. This refers to the local copy of f
. That is, evaluate the left hand side first.eval
call which returns undefined
but declares a new local function of f
.f
from f =
was evaluated before the the function itself, when we assign undefined to it we're actually replacing the global functionconsole.log
inside we're referring to the local copy declared in the eval since it's closer in the scope chain.console.log
, we are now referring to the 'global' f
which we assigned undefined to.The trick is, the f we're assigning to and the f that we're logging are two different fs. This is because the left hand side of an assignment is always evaluated first (sec 11.13.1 in the spec).
IE and Chrome make the mistake of assigning to the local f
. Which is clearly incorrect since the specification clearly tells us:
Let lref be the result of evaluating LeftHandSideExpression.
Let rref be the result of evaluating AssignmentExpression.
So, as we cal see the lref needs to be evaluated first.
(link to relevant esdiscuss thread)
I'm sorry but I can only answer your first question as of now :-/
I expected f to be defined at all times during execution. Why is f not always defined?
Two things:
eval
ing a function declaration does return undefined
. It may be redefined as it is evaluated, but you then assign undefined
to f
thereafter.f
is a local variable in the function f
, since named functions are available in their own scopes.Check this behaviour in http://jsfiddle.net/G2Q2g/5/.
So now you at least may ask
Why is it
undefined
when the secondconsole.log
is invoked in Firefox, as opposed to the correct behaviour in Opera, Chrome and IE?
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