The hidden scope of a named function expression

smlka

Andrey Smolko

Posted on January 25, 2022

The hidden scope of a named function expression

There are several syntaxes to create a function in JavaScript. One of them is called function expression:

const f = function() {}
Enter fullscreen mode Exit fullscreen mode

It is also possible to add a name inside function expression and such syntax is well known as named function expression:

const f = function internal(){}
Enter fullscreen mode Exit fullscreen mode

If a function is created like that then variable internal is available inside the function scope and is not available in the global scope:

const f = function internal(){
console.log(internal)
}
f(); // f internal(){...}
internal; // Reference error internal is not defined
Enter fullscreen mode Exit fullscreen mode

Ok cool, it is basic and looks pretty straightforward. But...

Where exactly is variable internal defined?

First idea - function scope

Let's say that variable internal is defined in the function scope. It is a decent guess as we have just checked that the variable is accessible only inside the function scope and is not accessible in the global one. But what if we create a constant and name it internal inside the function body:

const f = function internal(){
const internal = 100;
console.log(internal)
}
f();
// 100 in console
Enter fullscreen mode Exit fullscreen mode

The code does not throw any errors and it seems that we have just created a constant with the name internal in the scope which already had variable internal (from function name) successfully. But the issue is that JS does not allow to use const statement with an identifier which has already been used in var/let/const statement earlier in the code. So there are two ways to avoid that issue.

The first way is to assume that there is a special mechanism inside a function scope which controls creation and access to a variable instantiated from a function expression name (false).
The second - to use something elegant and already existing (true).

Second idea - intermediate scope

Actually there is only one way to get full and detailed information about JS internals. It is the ECMAScript Language Specification. Definitely it is not an easy reading and it requires some experience, but believe me, it is worth investing your time in it.

But before checking a description of a named function expression in the spec let's refresh one famous JS term - closure (yeap, again)

So closure is a function with a scope where the function is created. Actually every function in JS is a closure.

const b = 20
const f = function (){
const a = 10;
a;
b;
}
f()
Enter fullscreen mode Exit fullscreen mode

When function f is created it "learns" the surrounding scope. When function f is called a local function scope is created and then chained to the outer scope the function remembered during its creation:

Image description

A variable identifier resolution starts from a local function scope (const a). If a variable is not found in the local scope (const b), the resolution is delegated to the outer scope (global scope in the example). That is how the scopes are chained to each other. Cool, easy!

Let's jump back to a named function expression. There is a section describing creation of a named function expression.

There are key steps:

FunctionExpression : function BindingIdentifier ( FormalParameters ) { FunctionBody }

2. Set name to StringValue of BindingIdentifier.
3. Let outerEnv be the running execution context's LexicalEnvironment.
4. Let funcEnv be NewDeclarativeEnvironment(outerEnv).
5. Perform funcEnv.CreateImmutableBinding(name, false).

8. Let closure be OrdinaryFunctionCreate(...).

11. Perform funcEnv.InitializeBinding(name, closure).
12. Return closure.
Enter fullscreen mode Exit fullscreen mode

The main idea is to create an additional intermediate scope in a closure for keeping only one variable with a function name!

The following code example:

const b = 20
const f = function internal(){
const a = 10;
a;
internal;
b;
}
f()
Enter fullscreen mode Exit fullscreen mode

may be presented like that:

Image description

So this is an answer to our initial question: "Where exactly is variable internal defined?"

The variable internal is not available in the global scope and at the same time it does not block creation of a variable with the same name inside a function scope because the internal lives in its own scope between the global and the function scope. Win-win!

Final part

Ok, now we know that variable internal has its own scope but is it constant or variable? May we reassign a different value to it? Let's try something like that:

const f = function internal(){
internal = 100;
console.log(internal)
}
f()
Enter fullscreen mode Exit fullscreen mode

The identifier internal still contains a function and there are no errors. This is actually interesting and I admit that such a logic is quite unique.

According the spec, variable internal in our example is created via CreateImmutableBinding abstract method.

That method is used to create constants but also has a boolean flag as a second argument. If that flag is false then a different value cannot be assigned to the identifier. However, such an assignment does not throw an error. In case of a constant statement that flag is true and a reassignment throws TypeError.

💖 💪 🙅 🚩
smlka
Andrey Smolko

Posted on January 25, 2022

Join Our Newsletter. No Spam, Only the good stuff.

Sign up to receive the latest update from our blog.

Related