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 function f
we're in
f =
eval("" + f); // declare a new function f
in the scope of this function
f = undefined;
// since undefined === 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.
Why it works in strict mode
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
- "Find"
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.
- Perform the
eval
call which returns undefined
but declares a new local function of f
.
- Since
f
from f =
was evaluated before the the function itself, when we assign undefined to it we're actually replacing the global function
- So when we do
console.log
inside we're referring to the local copy declared in the eval since it's closer in the scope chain.
- When we're on the outside and do
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)