JavaScript Lexical Environment

JavaScript Lexical Environment

·

6 min read

In JavaScript, code in a script or a function body has a hidden object called the Lexical Environment object. The object can not be gotten from the code; It only describes how the code works.

There are two components of the lexical environment object, they are:

  • Environment record object
  • Reference to the outer (parent) lexical environment

Components of the lexical environment

An environment record object is an object that stores local variables as properties.

The interpretation of the examples below only exists theoretically and not practically.

See the example below:

function myName(otherName) {
  const lastName = 'Bello';
  const firstName = 'Osagie';
  console.log(`My name is ${lastName} ${firstName} ${otherName}`);
};

myName('Noah');

The environment object of the code above is interpreted as shown below:

globalEnvironment = {
  environmentRecord: {
    lastName: 'Bello',
    firstName: 'Osagie',
    otherName: 'Noah',
  },

// globalEnvironment has no outer reference 
outer: null // no parent environment => discarded by JS engine
};

During the execution, there are two lexical environments, the globalEnvironment and environmentRecord. That is the environmentRecord makes reference to the globalEnvironment during execution.

Generally,

globalObject = {
  ...
  parentObject: {
    ...
    childObject: {
      ...
    }
        ...  ...  ... 
        ...  ...  ... 
        ...  ...  ... 
  }
};

The global Lexical Environment has no outer reference, that’s why the outer is null.

The local variables are stored in the environmentRecord. The parameters also are stored in the environmentRecord. The outer is null because it has no parent object.

The global lexical environment is associated with the whole script.

Without the function, the code will still be within the global lexical environment.

See the example below:

const lastName = 'Bello';
const firstName = 'Osagie';

The environment object of the code above is interpreted as shown below:

globalEnvironment = {
  environmentRecord: {
    lastName: 'Bello',
    firstName: 'Osagie',
  }

outer: null
};

// globalEnvironment has no outer reference 
outer: null // no parent environment => discarded by JS engine

The environmentRecord is the parent object to lastName and firstName properties.

The outer is a property within the global object, globalEnvironment but no parent object to store its property.

The global Lexical Environment, globalEnvironment is for the entire script; while the environment record, environmentRecord (internal object) is for the local variable storage. The outer during execution is removed from memory by the JavaScript engine to save memory since it has no parent and it is unreachable or unused.

The global Lexical Environment has no outer reference, that’s why the arrow points to null.

The example below shows what happens when changes are made to the variables in a script.

See the example below:

// name; /* uninitialized state */
name = 'Bello'; // assigned a value
name = 'Osagie'; // changed the value

In an uninitialized state, the JavaScript engine is aware of the variable name but can not be referenced until it is declared with let.

The environment object of the code above is interpreted as shown below:

globalEnvironment = {
  environmentRecord: {
   // name: <uninitialized>,
    name: undefined,
    name: 'Bello',
    name: 'Osagie'
  };

outer: null
};

// globalEnvironment has no outer reference 
outer: null // no parent environment

outer is null because there's no reference to the outer lexical environment.

In the example above initially, the variable name was in an uninitialized state because it wasn't declared with the let keyword. When declared with let but not assigned a value, it became undefined. It was then changed from name='Bello' to name='Osagie' making it defined.


Inner and Outer Lexical Environment

Unlike an uninitialized variable that is not ready to be used until initialized with the let keyword, the function declaration immediately becomes ready to use from the start.

See the example below:

/*name;  // uninitialized */
let name = 'John';

function greet() { // initialized
  console.log(`Hello ${name}`); // Hello John
}

greet();

The environment object of the code above is interpreted as shown below:

globalEnvironment = {
  environmentRecord: {
      // [[Environment]]
      // name:  <uninitialized>,
      name: 'John',
       outer1: { 
        greet: function(name) { // initialized
          console.log(`Hello ${name}`);
        },
     } 
  },

// globalEnvironment has no outer reference 
outer: null // no parent environment => discarded by JS engine
};

The object outer1 above has its own hidden environment record for variables (object properties) has [[Environment]].

[[Environment]] keeps the reference to the Lexical Environment where the function was created. It is available in all functions.

{
  name: 'John',
  environmentRecord: { greet: [Function: greet], outer: null }
}

During execution, the inner Lexical Environment is searched first, then the outer one, then the more outer one, and so on until the global one.

The two lexical environment are globalEnvironment and environmentRecord.

  • The environmentRecord is the inner lexical environment that has a reference to outer1.
  • The globalEnvironment is the outer lexical environment it holds both the name variable and the function itself.

Let's see another example below:

function counterFunc() {
  let count = 0; // from each executed makeCounter() => 1, 2

    let counter = function() {
    // [[Environment]]
    count++; // 0 => 1, 2
    return count; // 1 => 2, 3
    // after each counter count, the count variable can be reached
  };

  return counter;
}

let makeCounter = counterFunc();
makeCounter() // 1 => count now 1, let count = 1
makeCounter() // 2 => count now 2, let count = 2
makeCounter() // 3 => count now 3, let count = 3

In the example above, at the beginning of each counterFunc() call, a new Lexical Environment object is created to store variables for this counter() run.

globalEnvironment = {
  environmentRecord: {
    count: 0, // from each executed counter.[[Environment]] => 1, 2
    outer1: { // environmentRecord = outer1
      counter: function() { // initialized
        [[Environment]],
          count++; // 0 initially, 1, 2
        return count; // { count: 1 } initially, { count: 2 }, { count: 3 } ...
      },
    },

    // globalEnvironment has no outer reference 
    outer: null // no parent environment => discarded by JS engine
  }
};

The counter.[[Environment]] has the reference to { count: 0 } Lexical Environment at first execution. Later, when counter() is called, a new Lexical Environment is created for the call, and its outer lexical environment reference is taken from counter.[[Environment]].

The outer2 is cleaned up from memory by the garbage collector since it has no reference to a parent object.

counter.[[Environment]] // 1 => move to outer Lexical Environment =>  globalEnvironment
counter.[[Environment]] // 2 => move to outer Lexical Environment =>  globalEnvironment
counter.[[Environment]] // 3 => move to outer Lexical Environment =>  globalEnvironment

In JavaScript, all functions are naturally closures except for the new Function syntax.


Buy me a Coffee