JS ES2015 min

Understanding ES-2015/ES6 Scope: Block Scope (Let and Const)

This article is targeted toward the audience who wants to know about Scope ES-2015/ES6 in JavaScript. Here, we mainly focus on what is Scope and how it works. We will discuss the following point.

  1. Introduction to scope.
  2. Scope representation through the diagram.
  3. JavaScript new scope (let, const).
  4. Hoisting in JavaScript.

One of the major features introduced by ES2015 is new scope. In this section, we will understand what a scope is. We’ll then move on to look at how we can create the new type of scope, and the benefits it can bring to our code.

Introduction to scope:-

The term scope defines an area in which variables, functions or identifiers can be accessed. JavaScript traditionally had two types of scope:- Global Scope and Function Scope.

var global = “This is global variable”;

function globalFunction1(){

var inner1 = “Non global variable 1”

}

function globalFunction2(){

var inner2 = “Non global variable 2”

}

In the above code, we have declared a variable global. This statement is not inside any function so this variable will automatically store in global scope.

The browser creates a global scope using the ‘window’ object.

So we can access the variable global either by using its identifier name global or by using a window object (window.global). We can access this variable anywhere in the file before or after, inside or outside the two functions. That’s why we can say that global variables are “visible” to all of our code and we can literally access them from anywhere.

After the variable global, we declare two functions, globalFunction1 and globalFunction2, these functions can be invoked in code anywhere else in this file. However, when the JavaScript engine parses these functions, it will create two new function scopes, one for each of the functions. This is where things get interesting. Scopes in JavaScript can be nested so the two new function scopes become child scopes of the global scope. This means that the code inside each function can access the global variable as if it had been declared inside the function alongside the inner variables.

When trying to access an identifier in JavaScript, the browser will first look for the variable inside the current scope. If it is not found, the browser will then look in the parent scope of the current scope and will keep moving up through the parent scopes until it reaches to the global scope. If the variable still isn’t found in the global scope, the browser will generate a Reference error. The nested scopes are known as a Scope Chain, and this process of checking the current scope and then the parent scopes is known as a “Variable look-up”. This look-up is only able to go up the scope chain, it never looks at child scopes of the current scope.

For example, variable inner1 can be accessed only inside the globalFunction1function, and inner2 can be accessed only inside the globalFunction2 function. Variable inner2 cannot be accessed within the globalFunction2 function, or by the global scope, and inner2 cannot be accessed by globalFunction1 or the global scope.

Below image is the representation of scope in the above code.

global function

In the above figure global scope contains a global variable as well as the two nested function scopes. Each nested function scope contains its own variable, but these variables are not accessible to the global scope.

Let’s look at another example.

function outer(){

var first_variable;

function inner(){

var sec_variable

}

}

In the above code, we have an outer function named outer, defined in the global scope. As it’s a function, it creates a function scope, which is inside the global scope. Inside the outer function, we declare a variable first_variable and a new function called inner. As we see that inner is also a function, a new scope is also created, and nested within the outer function’s scope.

Inside the inner function, we can access both sec_variable, which is in the inner function’s scope and first_variable. When we access first_variable from inside the inner function, the browser will first look for the variable in its current scope; when the variable is not found, the look-up will navigate up to the parent scope, which is the outer function’s scope. The scopes in this code could be represented like this:

scope variable

The scope chain is longer in this example, stretching from the inner function, through the outer function, and up to the global window object.

JavaScript new scope:

In JavaScript, a block is one or more statements within curly brackets. Conditional expressions, such as if, for, and while statements, all use blocks to execute statements based on certain conditions.

Other popular and common programming languages have block scope, so scope in JavaScript, which until now has only had global and function scope, has always been considered confusing. The ES2015 addition of block scope to JavaScript has important implications for our code and can also make the language more intuitive to developers familiar with other programming languages.

Block scope means that a block is able to create its own scope, rather than simply existing within the scope created by its nearest parent function, or the global scope. Let’s understand how scope has traditionally worked with blocks in JavaScript.

function outerFn(){

var scopeType1 = “This is function scope”;

if(true){

var scopeType2 = “This is not block scope”;

}

function innerFn(){

console.log(scopeType1, scopeType2) // function scope not block scope

}

innerFn()

}

The var statement is not able to create a block scope, even when used within a block, so the console.log statement is able to access both the scopeType1 and scopeType2 variables. The outerfn function creates a function scope and both the scopeType1 and scopeType2 variables are accessible via the scope chain within that scope.

Hoisting:-

Concept of hoisting is fundamental to understanding how JavaScript works. JavaScript has two phases: –

  1. Parsing phase (where all of the code is read by the JavaScript engine.)
  2. Execution phase (where the code that has been parsed is executed.)

During the parsing phase, there is memory allocation for variables, scope creation and hoisting of identifiers. Hoisting describes what happens when the JavaScript engine encounters an identifier, such as a variable or function declaration. When an identifier is encountered, it literally lifts (hoists) that declaration up to the top of the current scope.

Whereas during the second phase, most of the things happen like when you use a console.log statement, the actual log message is printed to the console during the execution phase.

function outerFn(){

var scopeType1;

var scopeType2;

 scopeType1 = “This is function scope”;

if(true){

scopeType2 = “This is not block scope”;

}

function innerFn(){

console.log(scopeType1, scopeType2) // function scope not block scope

}

innerFn()

}

Only the variable declaration is hoisted to the top of current scope. The variable assignment still occurs at the place where we assigned the value, inside the if statement in this example.

In addition to variables, function declarations are also hoisted. Consequently, from the JavaScript engine’s perspective, the code actually looks like this:

function outerFn(){

var scopeType1;

var scopeType2;

function innerFn(){

console.log(scopeType1, scopeType2) // function scope not block scope

}

scopeType1 = “This is function scope”;

if(true){

scopeType2 = “This is not block scope”;

}

innerFn()

}

The declaration of innerFn is also moved to the top of its scope. But remember, it is just the declaration of the function that is hoisted, not the invocation of the function. The above code won’t throw any errors because innerFn isn’t invoked until after the scopeType1 and scopeType2 variables have had values assigned to them.

Using Let:-

The var statement is not able to create a block scope. In order to create block scope, we need to use either the let or const statements inside a block.

Superficially, let is very similar to var and we use it to declare variables:

function introduceLet(){

var variable1;

let variable2

}

In the above example, the var and let statements both initialize a new variable in the current scope, which is the scope created by the introduceLet function. In order to create a new block scope, we need to use let inside a block:

function introduceLet(){

var variable1 = “function scope”

if(true){

let variable2 = “block scope”

}

console.log(variable1, variable2); //Uncaught ReferenceError: variable2 is not defined

}

introduceLet();

In the above case the code throws a reference error because the introduceLet  function creates a new scope within which variable1 is declared. After that, we have an if statement, which uses a block to declare variable2. However, because we used the let statement within that block, a new block scope is created within the introduceLet scope.

If the above console.log statement had been inside the if block as well, it would be in the same scope as variable2 and would be able to use the scope chain to find variable1. But because console.log is in the introduceLet scope, it can’t access variable2, so it throws a reference error.

Block scopes work the same as function scopes work, but they are created for blocks, rather than functions.

The Temporal Dead zone:-

When a regular variable created using var is hoisted to the top of its scope, and initialized with the value undefined, which is what allows us to be able to reference a normal variable before it has a value declared through assignment:

console.log(x); //undefined
var x = 10;

Remember, because of hoisting, the code looks like this:

var x = undefined;
console.log(x); //undefined
x = 10;

That’s why Reference Error is not thrown in JavaScript.
Variables declared with let are hoisted, but crucially, they are not automatically initialized with the value undefined, which means that the following code produces an error:

console.log(x); //Uncaught ReferenceError: x is not defined
let x = 10;

This error is caused by the Temporal Dead Zone (TDZ). The TDZ exists from the moment the scope is initialized to the moment the variable is declared. To fix the Reference Error, we need to declare the variable before trying to access it:

let x;
console.log(x); //undefined
x = 10;

The TDZ was designed like this in order to make development easier—trying to reference a variable that has not been declared yet is more commonly an error than an intentional decision, so the error highlights this to us immediately.

Using const:-

The const statement is used to declare a variable whose value cannot be reassigned but when being declared, a const variable must be initialized with a value:

const MAXIMUM = 100;
const company = “QSS TECHNOSOFT PVT. LTD.”

From this point on, the value of MAXIMUM will always be the number constant. If we try to change the value of the variable through reassignment, we’ll see an error.

(TypeError: Assignment to constant variable).

If we try to create a const variable without initializing it with a value, we’ll also see a SyntaxError.

(SyntaxError: Missing initializer in const declaration).

Similarly, a const variable cannot be redeclared. If we try to declare the same const variable more than once, we’ll see a different type of SyntaxError.

(SyntaxError: Identifier ‘Variable Name′ has already been declared).

Since let and const are both reserved words in JavaScript, that’s why they cannot be used as identifier names in strict mode.

As ES2015 becomes more and more common, a consensus is emerging that both let and const are superior to var because the scope of variables created with them is more aligned to other modern programming languages, and code behaves in a much more predictable way. Therefore, for most situations, it is preferable to avoid the use of var if possible.

Immutability:-

While the value of a const variable cannot be changed with reassignment, const variables are not completely immutable. If we initialize a const variable with an object or an array, we will still be able to set the properties of the object and add and remove items to the array.

const x = 10;

  x = 20;

const y = {name:”Prashant”}

 y = “Anything”

const z = {name: “Prashant”}

 z.name = “Anything”;

 z.company = “QSS Technosoft pvt. ltd.”

About Author:
Author Prashant Rai is a full-stack engineer currently working with QSS Technosoft. He has worked in Node.JS, GraphQl, Express.JS, Sequelize.JS, ReactJS, Apollo client, HTML, CSS and Git. He is always ready to explore new and upcoming technologies.

Tags: ,

Hire certified

Developers

  • Avg. 6+ Years of Experience.
  • Dedicated Resource on Demand.
  • NDA Protected Terms.
  • Start/Interview within 24 Hours.
  • Flexible Engagement Models.
  • Best code practices.
Start Now!
E-Book

eBook

6 Most Important Factors for a Successful Mobile App!

Every precaution that you take in the development process will help you to be effective in building products that will help you grow and reach your goals. Consider these 6 factors before heading for mobile app development.

Subscribe to our newsletter and stay updated

Loading