Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why does a function redefining itself behave differently in Chrome/IE and Firefox?

Tags:

javascript

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);
}
like image 715
Dagg Nabbit Avatar asked Jan 08 '14 22:01

Dagg Nabbit


Video Answer


2 Answers

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:

  1. f = eval("" + f); // set the left hand side to the function f we're in
  2. f = eval("" + f); // declare a new function f in the scope of this function
  3. 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:

  1. Let lref be the result of evaluating LeftHandSideExpression.

  2. 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)

like image 92
Benjamin Gruenbaum Avatar answered Sep 28 '22 04:09

Benjamin Gruenbaum


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:

  • evaling 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 second console.log is invoked in Firefox, as opposed to the correct behaviour in Opera, Chrome and IE?

like image 42
Bergi Avatar answered Sep 28 '22 05:09

Bergi