mariossimou.dev
HomeBlogContact

Scope, Lexical Environment and Variables Declaration in Javascript

Published on 6th November 2022

Front-end Development

Introduction

Although JavaScript supports more than one programming paradigm, it is widely known for its functional-oriented nature. In functional programming — and not only — , scope is an important concept that developers need to pay attention to. Is one of those things that distinguish an experienced programmer with a beginner. In addition, one of the most recent versions of JavaScript, ECMAScript2015 (ES6), has introduced two new features, the let and const declarations. These features have been created to resolve some of the problems related to var declaration and scope. In this article, I will try to explain the meaning of scope, define the relation between scope and lexical environment, and then to explain some of the differences between var, let, and const declarations.

Scope and Lexical Environments

When the JavaScript engine starts to parse JavaScript code, it starts from the very first line of a script, and moves line by line until it reaches the end. A script usually contains variables — any value that is remembered from the JavaScript engine and can be used in more than one place — and statements such as functions, loops, conditionals, try/catch . These statements define the logic of a script and often they are associated with certain variables. But, how does the JavaScript engine determine which variables go in each statement? This question is answered by the concept of scope, which sets the relationship between variables and statements, or simply defines the area where a variable is visible within a script.

In JavaScript, the concept of scope is implemented using Lexical Environments. A Lexical Environment is a structure that whenever it is created, it remembers those variables that are contained within the area that forces its creation. A lexical environment consists of two parts:

  • Environment record — an object that has all local variables as its properties

  • A reference to the outer lexical environment, usually the one associated with the code lexically right outside of it.

For instance, when a function is invoked in JavaScript, it forces the creation of a lexical environment that is associated with it. That environment contains an environment record that keeps track of those variables that exist within the function in the form of key-value pairs (object). Additionally, the environment record has a pointer to an outer lexical environment, which may refer to null (none) or another lexical environment. If it is the latter, it means that the function is an inner function nested to an outer function, which outer function forces the creation of its own lexical environment as well. With that way, we start creating a chain of lexical environments, called scope chain, and allows us to maintain the order in which the JavaScript engine looks for variables.

const outer = () => {
      const name = 'John'
      return (()=> {
          const surname = 'Doe'
          return `Hello ${ name} ${ surname }`
 	})() // IIFE
 }
  
 const name = 'Paul'  
 console.log(outer()) // Hello John Doe

The code will return Hello John Doe. When the outer function is called, it starts by creating a lexical environment. That lexical environment contains the value of name and the IIFE function. Now, when the JavaScript reaches the IIFE, it executes the function and creates a second lexical environment. That environment contains the variable surname. As soon as the JavaScript engine reaches the return keyword, it looks for variables name and surname. Initially, it starts to look in the lexical environment of the inner function — the environment created at the end — and will find the value of the surname, Doe. Because it cannot find the value of a name, it moves to the outer lexical environment and use the value of the name — John — . Since the engine has found both of the values that it needs, it returns the string populated with those values — Hello John Doe —.

What if the value of name did not exist in the outer lexical environment of the function? The answer is simple. Because the environment related to the outer function does not contain the value that the engine needs, it will move to the global lexical environment, which contains the variable name with the value Paul. This means that it will return Hello Paul Doe.

var, let and const

So far so good. Now, a lexical environment is created only when:

  • The script loads, creating a global lexical environment.

  • A function statement is invoked, creating a lexical environment bound to the code within the function, no matter how a variable may be declared.

  • A combination of let, const declarations with code blocks {...}, which exist in statements like if/else, loops,try/catch etc.

for( var i = 0; i < 5; i++){
	setTimeout( ()=> console.log( i ) , 1000) // 5,5,5,5,5
}
      
for( let i = 0; i < 5; i++){
	setTimeout( ()=> console.log( i ) , 1000) // 0,1,2,3,4
}

That is the main reason why let and const declarations are introduced in ECMAScript2015 (ES6). In contrast with the var declaration, if let and const keywords exist within code blocks, they force the creation of a new lexical environment. See the two example below:

Why is this happening? In both loops we execute some asynchronous code using the timer function setTimeout. We add a delay of 1 second, and then we display the value of i. Since var declaration does not create a lexical environment, the variable i will exist in the global lexical environment, which is accessed by all iterations. Therefore, when we print the value of i (after 1 second), the JavaScript engine reads the latest modified value of i , which is 5. This value is used for each iteration, printing 5 for five times.

Since let declaration creates a lexical environment, the value of i is contained within that specific environment. This means that on each iteration, a new lexical environment is created, which will be independent from one another. Therefore, when the value of i is printed after 1 second, the JavaScript engine will find the value of i in the lexical environment associated with the iteration. Each iteration will have the correct value of i and therefore it will print 0,1,2,3,4.

I want to note that variable i is included in the scope of if statement. Althought it looks like it is declared outside of it, in reality the JavaScript engine considers it inside the statement.

It is commonly said that a variable in a code block {...}, declared with let and const keywords, it has a block scope. On the other hand, if the variable is declared with var, it has a function scope, as it is contained in the nearest function lexical environment. If it's not contained within a function, then the variable has a global scope and contained in the global lexical environment.

Another good example is shown below:

if ( true ){
  const name = 'John'
}
console.log( name ) // Error
if ( true ){
  var name = 'John'
}
console.log( name ) // John

In the first example, the var declaration does not create a lexical environment, which means that the value of name exists in the global environment — this behaviour is called hoisting and will be discussed in another tutorial —. When we print the value of name, the engine looks in the global lexical environment and returns John. This is not the case for the second example. Due to const declaration creating a separate lexical environment, the variable of name exists only within that environment, which is visible only within the curly braces. As soon as the JavaScript engine moves out of the if statement, it does not have access to that environment, and when attempts to find the value of name, it looks into the global lexical environment. The global environment does not contain the variable name, and therefore the engine returns an error — while in strict mode the engine returns an error, in any other case it will return undefined —.

Lastly, the difference between let and const declarations lies in the immutability property. Any variable declared with const keyword is immutable, meaning that the value of the variable cannot change. On the other hand, let keyword allows the value to be changed. In both cases, a variable with the same name cannot be declared again.

Sources

Designed by
mariossimou.dev
All rights reserved © mariossimou.dev 2023