Reading the ECMA-262: How Declarations in Function Context Work

Published on

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 )
  2. If F.[[IsClassConstructor]] is true, throw a TypeError exception.
  3. Create a new callee execution context (PrepareForOrdinaryCall ( F, newTarget ))
    1. callerContext = the running execution context.
    2. calleeContext = a new ECMAScript code execution context.
      1. calleeContext.Function ← F
      2. calleeContext.RealF.[[Realm]]
      3. calleeContext.ScriptOrModuleF.[[ScriptOrModule]].
    3. Let localEnv be NewFunctionEnvironment(F, newTarget = undefined).
      1. localEnv.outerEnvironment is F.[[Environment]] (the declarative environment where the function declaration is instantiated in)
      2. calleeContext.LexicalEnvironmentlocalEnv.
      3. calleeContext.VariableEnvironmentlocalEnv.
    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).
    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).
    1. FunctionDeclarationInstantiation(F, argumentsList)
      1. calleeContext = the running execution context env = calleeContext.LexicalEnvironment envRec = env.EnvironmentRecord
      2. hasParameterExpressions = 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)

            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

      // 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 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)

          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