JavaScript Code Nesting and Lexical Environments Explained

JavaScript Code Nesting and Lexical Environments Explained

Nesting in JavaScript

Remember that JavaScript is a functional programming language (well mostly 😉 ). So when we refer to nesting in JavaScript, we are referring to the ability to create a function within a function. Here is a brief example.


//a simple example, but this should get the point across...
function outerFunction(){
  console.log("I'm the outer function");
  function innerFunction(){
    console.log("I'm the inner function");
  }
  innerFunction();
}

outerFunction();

In the world of JavaScript, you will work with this sort of a situation all the time, take a look also at functions using the fat arrow syntax. This is a fairly commonplace use of this type of programming with the newer ES6 standards, just remember that this is not what you might think it is using arrow functions, and that is a subject for another post!

Example of nesting using an arrow syntax:

function addFirstArgToTwenty(n){ 
  let f = (...args) => { return args[0] + n }; //using a spread operator to return the first argument and add `n`
  return f(20); 
}

console.log(addFirstArgToTwenty(42,3));//62

If you come from many other programming languages especially a C-Style language, you should be familiar with block scopes and scoping of variables. JavaScript does things a little differently, and you may have run into situations and in fact, I would almost guarantee you have if you are unaware of lexical environments and how closures work.

For example in JavaScript realize that there is not a way to exactly create a private variable, instead, we use closures and the resulting lexical environment to achieve this. In other words, we can use closures to achieve the desired private nature of a variable. So next, let’s look at exactly what we mean by closures and the resulting lexical environment.

Closures and Lexical Environments

In this portion of the post, we’ll cover closures and lexical environments and why they are important to us as JavaScript developers.

What is a Closure?

Simply put, a closure in JavaScript is the { curly braces } in your code. This may sound really simple, and it is, yet there are a couple of gotchas along the way you should be aware of. Closures and lexical environment are related to one another in that closures will affect the scope of your variables and functions (remember that functions are first class objects!), in other words, closures affect where your variables and function are available to be used. In other words, the generically used term scope is your lexical environment in JavaScript, but it is not necessarily limited to the {} in your code. For example, look at the next snippet of code to see how this works, and notice that our i variable is accessible outside the for loop, often times this is called hoisting, but that seems to be a bit simplistic once you understand the why. This is because the lexical environment or scope is actually created in the declaration of our callForLoop(){} function.

var outerVariable = "I'm an outer variable";
function callForLoop(){
  var innerVariable = "I'm an inner variable";
  console.log(outerVariable); //able to do this, makes sense right. 
  for (var i = 0; i > 2; i++){
    console.log(i, "inner loop") 
  }
  console.log(i, "outer loop");//2 "outer loop" and still accessible
}

callForLoop();
console.log(innerVariable); //will throw an Uncaught Reference Exception

Notice that the last piece where we are console.log(innerVariable); throws the exception. This is because it falls outside the scope of our function.

That should be pretty straightforward, however in this context with the variable i we are able to call that outside our for loop and still retrieve the value in this line of code console.log(i, "outer loop");//2 "outer loop". How is this possible? Well, that is because of the lexical environment or execution context of our code.

What is actually occurring is that the context of our function is being called and the closure creates the Lexical environment for all variables and functions within. So the function callForLoop(){}; function is actually creating it’s own storage for variables. We are able to grab variable and function that are outside the closure, including global properties as well as those declared within. Now the for loop is a part of the overall lexical environment for our function callForLoop(){}; and therefore i is accessible anywhere within the closure. This has often times created confusion, and so with ES6 the let and const variable declarations were introduced. These have led to stronger enforcement of blocking and scoping of our variables and using them limits variable scopes to the closures they are declared or used in.

Same code, but now with let instead of var for an example of the what is more commonly seen in other languages that many developers come from and may not be aware of the concept of closures.

function callForLoop(){
  for (let i = 0; i > 2; i++){
    console.log(i, "inner loop") 
  }
  console.log(i, "outer loop");//Uncaught reference error will be thrown
}

callForLoop();

Execution Context

So let’s just take another look at the execution context or the call-stack and try to understand a little bit more about what effect this has on our Lexical Environment. I have thrown the below piece of code into chrome and using debugging tools I am looking at our call-stack or execution context.


For each breakpoint that I have commented above, you can see the call-stack images from chrome below. Bear in mind that we step through our code, and the first environment created is the global environment or scope, then we enter into our closure of our outerFunction(){...} which creates a new scope or lexical environment which has access to its parent environment, and then again into our inner function closure, which creates another lexical environment within our outerFunction(){...}. Once the innerFunction(){...} has completed that lexical environment is done away with and we have are again left with the global scope and the outerFunction(){...} scope or the lexical environment, and then stepping out of that we are again left with only the globally scoped lexical environment.

Keep in mind that functions and variables that are available will change with the scope. for example, the innerVariable is only available within the innerFunction scope or Lexical Environment. And the issue with variable hoisting is really a result of variables falling out of loop logic into the parent function closure, where they can then become available outside the loop. This is where let and const take care of that if it really throws you for a loop. I do prefer to use let in those situations, but you will be running into var and should understand this behavior in depth.

I hope you have found this useful and valuable. Please leave a comment and let me know what you think!

Next On FoC

C# Generics And Constraints

C# Generics And Constraints
Previously On FoC

Raspberry Pi Git Server

Raspberry Pi Git Server