Where is the immutable binding record of the identifier in a named function expression stored in JavaScript?
Asked Answered
L

1

4

Recently I ran into some interesting facts about named function expressions (NFE). I understand that the function name of an NFE can be accessed within the function body, which makes recursion more convenient and saves us arguments.callee. And the function name is not available outside the function body. For example,

var foo = function bar() {
    console.log(typeof bar);
}; 

typeof foo; // 'function'
typeof bar; // 'undefined', inaccessible outside the NFE
foo(); // 'function', accessible inside the NFE

This is a well-documented feature, and kangax has a wonderful post about NFE and mentioned this phenomenon there. What surprises me most is that the function name of an NFE cannot be re-associated with other values in the function body. For example,

(function foo() {
    foo = 5;
    alert(foo);
})(); // will alert function code instead of 5

In the above example, We tried to rebind the identifier foo with another value 5. But this fails! And I turned to ES5 Spec and found that an immutable binding record was created and added into the environment records of lexical environment when an NFE is created.

The problem is, when an NFE refers to its own function name inside the function body, the name was resolved as a free variable. In the above example, foo is referred to inside the NFE, but it is neither a formal parameter nor a local variable of this function. So it's a free variable and its binding record can be resolved through the [[scope]] property of the NFE.

So consider this, if we have another identifier with the same name in the outer scope, there seems to be some conflict. For example,

var foo = 1;
(function foo() {
    alert(foo);
})(); // will alert function code rather than 1
alert(foo); // 1

When we execute the NFE, the free variable foo was resolved to the function it is associated to. But when the control exits the NFE context, foo was resolved as a local variable in the outer scope.

So my question is as follows:

  1. Where is the immutable binding record of the function name stored?
  2. How come the function name foo outweigh var foo = 1 when resolved inside NFE? Are their binding records stored in the same lexical environment? If so, how?
  3. What's behind the phenomenon that function name foo is accessible inside but invisible outside?

Can someone shed some light on this with ES5 spec? I don't find much discussion online.

Lustral answered 30/5, 2015 at 14:46 Comment(0)
A
2

Where is the immutable binding record of the function name stored?

In an extra lexical environment record that you cannot see :-)

How come the function name foo outweigh var foo = 1 when resolved inside NFE?

In fact it doesn't. You can declare a new local var foo in the function scope without any collisions, but if you don't then the free foo variable does resolve to the immutable binding. It does outweigh global foo variables higher in the scope chain, however.

var foo = 1;
(function foo() { "use strict";
    var foo = 2;
    console.log(foo); // 2
}());
(function foo() { "use strict";
    console.log(foo); // function …
    foo = 2; // Error: Invalid assignment in strict mode
}());

Are their binding records stored in the same lexical environment?

No. Every named function expression is enclosed in an extra lexical environment that has a single, immutable binding for the function's name initialised with the function.

This is described in the Function Definition (§13) section of the spec. While the steps for function declarations and anonymous function expressions basically are "create a new function object with that function body using the current execution context's lexical environment for the Scope", named function expressions are more complicated:

  1. Let funcEnv be the result of calling NewDeclarativeEnvironment passing the running execution context’s Lexical Environment as the argument
  2. Let envRec be funcEnv’s environment record.
  3. Call the CreateImmutableBinding(N) concrete method of envRec passing the Identifier of the function as the argument.
  4. Let closure be the result of creating a new Function object […]. Pass in funcEnv as the Scope.
  5. Call the InitializeImmutableBinding(N,V) concrete method of envRec passing the Identifier of the function and closure as the arguments.
  6. Return closure.

It does construct an extra wrapper environment just for the function expression. In ES6 code with block scopes:

var x = function foo(){};
// is equivalent to
var x;
{
    const foo = function() {};
    x = foo;
}
// foo is not in scope here

What's behind the phenomenon that function name foo is accessible inside but invisible outside?

The foo immutable binding is not created in the current execution context's lexical environment, but in the wrapper environment which is only used for the closure around the function expression.

Ana answered 30/5, 2015 at 15:20 Comment(1)
Wow, wonderful answer, @Bergi! Thanks for clarifying my doubts!Lustral

© 2022 - 2024 — McMap. All rights reserved.