skip to Main Content

Lexical Environment & Closure

Code Blocks

If a variable is declared inside a code block

{...}

it’s only visible inside that block.

For example:

{
  let message = "Hello";
  alert(message);
}
alert(message);

We can use this to isolate a piece of code that does its own task, with variables that only belong to it:

{
  let message = "Hello";
  alert(message);
}

{
  let message = "Goodbye";
  alert(message);
}

For if, for, while and so on, variables declared in {…} are also only visible inside:

if (true) {
  let phrase = "Hello!";

  alert(phrase); // Hello!
}

alert(phrase); // Error, no such variable!

Nested Functions

A function is called”nested” when it is created inside another function. It is easily possible to do this with JavaScript. We can use it to organize our code, like this:

function sayHiBye(firstName, lastName) {

  // helper nested function to use below
  function getFullName() {
    return firstName + " " + lastName;
  }

  alert( "Hello, " + getFullName() );
  alert( "Bye, " + getFullName() );

}
Here the nested function getFullName() is made for convenience. It can access the outer variables and so can return the full name. Nested functions are quite common in JavaScript.

What’s much more interesting, a nested function can be returned: either as a property of a new object or as a result by itself. It can then be used somewhere else. No matter where, it still has access to the same outer variables.

Below, makeCounter creates the “counter” function that returns the next number on each invocation:

function makeCounter() {
  let count = 0;

  return function() {
    return count++;
  };
}


let counter = makeCounter();
alert( counter() ); // 0
alert( counter() ); // 1
alert( counter() ); // 2
Despite being simple, slightly modified variants of that code have practical uses, for instance, as a random number generator to generate random values for automated tests.
How does this work? If we create multiple counters, will they be independent? What's going on with the variables here?
Understanding such things is great for the overall knowledge of JavaScript and beneficial for more complex scenarios. So let's go a bit in-depth

Lexical Environment

Every execution context has a reference to its outer environment, and that outer environment is called Lexical Environment.

We can say that whenever an execution context is created to execute the code along with it a lexical environment is also created by the JavaScript engine. Each lexical environment has reference to its parent lexical environments.

A lexical environment can be considered as a data structure which holds identifier-variable mapping (identifier => name of the variable/functions). It has two components:

  1. Environment Record – Actual place where the variable and function declarations are stored
  2. Reference To Outer Environment – it has access to its outer (parent) lexical environment. This component is the most important in order to understand how closures work.

A lexical environment object looks like this:

lexicalEnvironment = { 
environmentRecord: { 
<identifier> : <value>, 
<identifier> : <value> 
} 
outer: < Reference to the parent lexical environment> 
}

Closure

A closure is a function that remembers its outer variables and can access them. In some languages, that’s not possible, or a function should be written in a special way to make it happen. But as explained above, in JavaScript, all functions are naturally closures (there is only one exception, to be covered in The “new Function” syntax).

Usually, a function remembers where it was born in the special property [[Environment]]. It references the Lexical Environment from where it’s created (we covered that in the chapter Variable scope, closure).

But when a function is created using new keyword, its [[Environment]] is set to reference not the current Lexical Environment, but the global one.

function getFunc() {
  let value = "test";
  let func = new Function('alert(value)');
  return func;
}

getFunc()(); // error: value is not defined

function person() {
    let name = 'Peter';
    return function displayName() {
        console.log(name);
    };
}
let peter = person();
peter(); // prints 'Peter'

When person()  is executed, the JavaScript engine creates a new execution context and a lexical environment for the function. After this function finishes, it returns displayName() and assigns it to peter variable.When the person()Γö¼├í finishes, its execution context is removed from the stack. But its lexical environment is still in the memory because its lexical environment is referenced by the lexical environment of displayName() . So name variable is still accessible.

Posted By: Shubham Kumar Jha, Osmosee

This Post Has 0 Comments

Leave a Reply

Your email address will not be published. Required fields are marked *

Search