03. Environment

Variables come into existence when program execution enters their scope. Then they need storage space. The data structure that provides that storage space is called an environment in JS. It maps variable names to values. Its structure is very similar to that of JS objects.

  • Environments provide storage space for variables;

  • There is one environment per scope.

  • Environments are managed as a stack.

  • The environment on top of that stack is considered active.

  • Environments sometimes live on after you leave their scope.

  • Environments are stored on a heap, not on a stack.

Invoking functions

Every time a function is invoked, it needs new storage for its parameters and variables. After it is finished, that storage can usually be reclaimed. As an example, function calls itself recursively several times and each time, it needs fresh storage for n:

function fac(n) {
  if (n <= 1) {
    return 1;
  }
  return n * fac(n - 1);
}

Surrounding environments

No matter how often a function is called, it always needs access to both its own (fresh) local variables and the variables of the surrounding scopes.

For example, the following function, doNTimes, has a helper function, doNTimesRec, inside it. When doNTimesRec calls itself several times, a new environment is created each time. However, doNTimesRec also stays connected to the single environment of doNTimes during those calls (similar to all functions sharing a single global environment). doNTimesRec needs that connection to access action in line (1):

function doNTimes(n, action) {
  function doNTimesRec(x) {
    if (x >= 1) {
      action(); // (1)
      doNTimesRec(x - 1);
    }
  }

  doNTimesRec(n);
}

Environment implementation

These two dimensions are handled as follows:

  • Dynamic dimension: stack of execution contexts. Each time a function is invoked, a new environment is created to map identifiers (of parameters and variables) to values. To handle recursion, execution contexts — references to environments — are managed in a stack. That stack mirrors the call stack.

  • Lexical dimension: chain of environments. To support this dimension, a function records the scope it was created in via the internal property [[Scope]]. When a function is called, an environment is created for the new scope that is entered. That environment has a field called outer that points to the outer scope’s environment and is set up via [[Scope]]. Therefore, there is always a chain of environments, starting with the currently active environment, continuing with its outer environment, and so on. Every chain ends with the global environment (the scope of all initially invoked functions). The field outer of the global environment is null. To resolve an identifier, the complete environment chain is traversed, starting with the active environment.

function myFunction(myParam) {
    var myVar = 123;
    return myFloat;
}
var myFloat = 1.3;
// Step 1
myFunction('abc'); // Step 2
> 1.3

The dynamic dimension of variables is handled via a stack of execution contexts, and the static dimension is handled by chaining environments. The active execution contexts, environments, and functions are highlighted.

  • Step 1 shows those data structures before the function call myFunction(abc).

  • Step 2 shows them during the function call.

Last updated