---
title: "Reading the ECMA-262: How Declarations in Function Context Work"
description: "To understand hoisting and variable scoping better, let's dive into the ECMAScript specification of function declarations."
lang: "en"
tags: ["JavaScript", "ECMA-262"]
pubDate: 2024-06-30T00:00:00.000Z
---

There are four kinds of context in which variable or function declarations would occur: Global, module, function, and block. This article focuses on the **function** context.

## Steps in Specification of Declarations in Function Context

When a function is called in JavaScript, the engine should prepare a new execution context for it. Here are the steps in the specification of how a context is prepared for a function call before its execution and how it is removed when the function call is completed:

1. [`[[Call]] ( thisArgument, argumentsList )`](https://262.ecma-international.org/13.0/index.html#sec-ecmascript-function-objects-call-thisargument-argumentslist)
2. If `F.[[IsClassConstructor]]` is `true`, throw a TypeError exception.
3. Create a new callee execution context ([PrepareForOrdinaryCall ( F, newTarget )](https://262.ecma-international.org/13.0/index.html#sec-prepareforordinarycall))
   1. `callerContext` = the running execution context.
   2. `calleeContext` = a new ECMAScript code execution context.
      1. `calleeContext.Function` ← F
      2. `calleeContext.Real` ← `F.[[Realm]]`
      3. `calleeContext.ScriptOrModule` ← `F.[[ScriptOrModule]]`.
   3. Let `localEnv` be [`NewFunctionEnvironment(F, newTarget = undefined)`](https://262.ecma-international.org/13.0/index.html#sec-newfunctionenvironment).
      1. `localEnv.outerEnvironment` is `F.[[Environment]]` (the declarative environment where the function declaration is instantiated in)
      2. `calleeContext.LexicalEnvironment` ← `localEnv`.
      3. `calleeContext.VariableEnvironment` ← `localEnv`.
   4. If `callerContext` is not already suspended, suspend callerContext.
   5. Push `calleeContext` onto the execution context stack;
      `calleeContext` is now the running execution context.
4. Perform [`OrdinaryCallBindThis(F, calleeContext, thisArgument)`](https://262.ecma-international.org/13.0/index.html#sec-ordinarycallbindthis).
   1. Let `thisMode` be `F.[[ThisMode]].`
   2. If `thisMode` is `lexical`, return NormalCompletion(undefined).
   3. If `thisMode` is strict, let `thisValue` be `thisArgument`.
   4. `calleeContext.LexicalEnvironment.envRec.BindThisValue(thisValue)`.
5. Let result be [`OrdinaryCallEvaluateBody(F, argumentsList)`](https://262.ecma-international.org/13.0/index.html#sec-ordinarycallevaluatebody).
   1. [`FunctionDeclarationInstantiation(F, argumentsList)`](https://262.ecma-international.org/13.0/index.html#sec-functiondeclarationinstantiation)
      1. `calleeContext` = the running execution context
         `env` = `calleeContext`.LexicalEnvironment
         `envRec` = env.EnvironmentRecord
      2. `hasParameterExpressions` = [`ContainsExpression`](https://262.ecma-international.org/13.0/index.html#sec-static-semantics-containsexpression) of `func.[[FormalParameters]]`
         - `hasParameterExpressions` is `false`:
           - `varEnv` = `lexEnv` = `env`
             > NOTE: Only a single lexical environment is needed for the parameters and top-level vars.
         - `hasParameterExpressions` is `true`:
           - `varEnv` = `lexEnv` = [`NewDeclarativeEnvironment(env)`](https://262.ecma-international.org/13.0/index.html#sec-newdeclarativeenvironment)
             > NOTE: A separate Environment Record is needed to ensure that closures created by expressions in the formal parameter list do not have visibility of declarations in the function body.
      3. Declarations
         1. vars: `varEnv.CreateMutableBinding` for `VarDeclaredNames` of `func.[[ECMAScriptCode]]` and initialize them with `undefined`
         2. lexical: `lexEnv.CreateImmutableBinding` or `lexEnv.CreateMutableBinding` for `LexicallyScopedDeclarations` of `func.[[ECMAScriptCode]]` (not initialized)
         3. functions: Create function objects for function declarations of `VarScopedDeclarations` of `func.[[ECMAScriptCode]]` with `LexicalEnvironment` as function object’s `[[Environment]]`. And `varEnv.SetMutableBinding` with these function objects.
6. Remove `calleeContext` from the execution context stack and restore `callerContext` as the running execution context.
7. If result.[[Type]] is return, return `NormalCompletion(result.[[Value]]).
ReturnIfAbrupt(result)`.
8. Return `NormalCompletion(undefined).`

We can see the same behavior of “hoisting” in a global context:

1. Lexical declarations are only instantiated here but not initialized.
2. Function declarations are instantiated and initialized with the function object as its value.
3. Var declarations are instantiated and initialized with `undefined` as its value.

However, the `LexicalEnvironment` and `VariableEnvironment` here are `calleeContext`’s `LexicalEnvironment`, which are different from the global ones. This is related to the concept of “scope,” which has not been covered yet.

## Recap

Here is the outline of the steps to prepare a new execution context for a function call:

1. Throw a “TypeError exception” if the function is a class constructor.
2. Create a new ECMAScript code execution context for Callee.
3. `calleeContext.LexicalEnvironment.envRec.BindThisValue(thisValue)`
4. Initiate the declarations in the function:

   1. Parameters: `func.[[FormalParameters]]`
      1. bound to `calleeContext.LexicalEnvironment.envRec`
   2. When a function has parameter expressions, other variables are bound to the new inner Declarative Environment’s record to ensure that closures created in the parameter list do not have visibility of declarations in the function body. Otherwise bound to `calleeContext.LexicalEnvironment.envRec`

      ```jsx
      // Example of a function with parameter expressions
      function outer(param = () => innerVar) {
        // Function body
        let innerVar = "I am inside the function body";

        return param();
      }

      // Call the function and see what happens
      try {
        console.log(outer()); // This will throw a ReferenceError
      } catch (e) {
        console.log(e.message); // innerVar is not defined
      }
      ```

   3. `hasParameterExpressions` = [`ContainsExpression`](https://262.ecma-international.org/13.0/index.html#sec-static-semantics-containsexpression) of `func.[[FormalParameters]]`
      - `hasParameterExpressions` is `false`:
        - `varEnv` = `lexEnv` = `env`
          > NOTE: Only a single lexical environment is needed for the parameters and top-level vars.
      - `hasParameterExpressions` is `true`:
        - `varEnv` = `lexEnv` = [`NewDeclarativeEnvironment(env)`](https://262.ecma-international.org/13.0/index.html#sec-newdeclarativeenvironment)
          > NOTE: A separate Environment Record is needed to ensure that closures created by expressions in the formal parameter list do not have visibility of declarations in the function body.
   4. Declarations
      1. vars: `varEnv.CreateMutableBinding` for `VarDeclaredNames` of `func.[[ECMAScriptCode]]` and initialize them with `undefined`
      2. lexical: `lexEnv.CreateImmutableBinding` or `lexEnv.CreateMutableBinding` for `LexicallyScopedDeclarations` of `func.[[ECMAScriptCode]]` (not initialized)
      3. functions: Create function objects for function declarations of `VarScopedDeclarations` of `func.[[ECMAScriptCode]]` with `LexicalEnvironment` as function object’s `[[Environment]]`. And `varEnv.SetMutableBinding` with these function objects.

5. Evaluate the statements in the function body
6. Pop the callee execution context from stack and resume the caller execution context as the running execution context
