Why use Let and Const Rather than Var...

Why use Let and Const Rather than Var...

Although var used to be the provided way to declare variables in JavaScript. There are many pitfalls with using the var syntax for declaring your variables, objects or arrays. There are some serious issues with using the var keyword, and should not be used in your application code!

In the bad old days, the only way you could declare a variable in JavaScript was to use the var keyword:

var i = 10;
var name = 'pedbad';
var obj = {};
var arr = [];

The major issue with using var is that it was not block scoped. In JavaSCript before the introduction of ES6 only function scope and global scope were available.

So what is scope? Scope is the context in which variables and functions are accessible. Scope determines the accessibility (visibility) of variables. JavaScript has three main types of Scope:

Global scope

A variable with Global scope is visible everywhere. All scripts and functions can access it. A variable declared outside a function becomes global.

Function scope

Variables defined inside a function are not accessible (visible) from outside the function.

Block scope

Variables declared inside a { } block cannot be accessed from outside the block

One problems with declaring variables with the var keyword is that you can easily overwrite variable declarations:

var studentName = 'John';
//sometime later in your code, 
//you can accidentally overwrite your variable
//by re-declaring it 
var studentName = 'Jane';
console.log(studentName);
//output :  Jane

In the code above, the studentName variable is initially declared as John, but is then accidentally redeclared to be Jane. The console then displays the string Jane.

In Javascript, it doesn’t matter how many times you use the keyword var. If it has the same name in the same function, you are pointing to the same variable.

In non trivial applications as your codebase becomes very large, you might accidentally overwrite a variable that you did not intend to change. Because this behaviour does not throw an error, searching for and fixing bugs becomes more difficult.

When you declare a variable with the var keyword, it is declared globally, or locally if declared inside a function.

function demo() {
  var name = "pedbad";
  console.log(`inside demo function our name variable is: ${name}`);
}

demo();
//output
//inside demo function our name variable is: pedbad

console.log(`outside demo function our name variable is: ${name}`)
//output
//ReferenceError: name is not defined

As expected the name variable declared inside the demo() function is only accessible (visible) from within the function. However as stated earlier, variables defined inside a function are not accessible from outside the function.

Now lets look at a { } block scope example:

{
  var foo = 'bar';
  console.log(`inside our block foo is:  ${foo}`);

}

console.log(`outside our block foo is:  ${foo}`);

//output 
//inside our block foo is:  bar
//outside our block foo is:  bar

Although we declared our foo variable inside the curly braces, we could still access the variable outside the block. This is a serious problem when using the var keyword. You would expect the foo variable to be private inside the block, but as stated earlier before the introduction of ES6 JavaScript did not support block scope.

Prior to ES6, the only way we could scope the variable foo to the block only was to use an Immediately Invoked Function Expression or (IIFE).

Using this design pattern we could scope our variable inside the block:

(function () {
  var foo = 'bar';
  console.log(`inside our block foo is:  ${foo}`);
})();

//output:
//inside our block foo is:  bar


console.log(`outside our block foo is:  ${foo}`);

//output
//ReferenceError: foo is not defined

Lets look at another issue with using var :

var numArray = [];
for (var i = 0; i < 5; i++) {
  numArray.push(i);
}

console.log(numArray);
//output:
//[ 0, 1, 2, 3, 4 ]

console.log(i);
//output:
//5

Our iterator variable i is still accessible outside the for loop! With the var keyword, i is declared globally. So when i++ is executed, it updates the global variable. This code is similar to the following:

var numArray = [];
var i;
for (i = 0; i < 5; i++) {
  numArray.push(i);
}

console.log(numArray);
//output:
//[ 0, 1, 2, 3, 4 ]

console.log(i);
//output:
//5

This behaviour will cause problems if you were to create a function and store it for later use inside a for loop that uses the i variable. This is because the stored function will always refer to the value of the updated global i variable.

var printNumZero;
for (var i = 0; i < 5; i++) {
  if (i === 0) {
    printNumZero = function() {
      return i;
    };
  }
}
console.log(printNumZero());

//output:
//5

As you can see, printNumZero() prints 5 and not 0. This is because the value assigned to i was updated and the printNumZero() returns the global i and not the value i had when the function was created in the for loop.

Lets explore another example:

for (var i = 0; i < 5; i++){
  setTimeout(function(){
    console.log(`inside for loop i is: ${i}`);
  }, i*1000);
}

//output:
//inside for loop i is: 5
//inside for loop i is: 5
//inside for loop i is: 5
//inside for loop i is: 5
//and so on...

Again, as our variable i is not block scoped, we only see 5 as our output. This is because by the time the setTimeOut function is called, our variable i has already been assigned the value of 5.

These are all serious issues with using var and that is one of the reasons in ES6 let and const were introduced to enable block scoping in JavaScript.

Lets take a look at our setTimeout example , this tile using the let syntax instead of var

for (let i = 0; i < 5; i++){
  setTimeout(function(){
    console.log(`inside for loop i is: ${i}`);
  }, i*1000);
}

//output:
//inside for loop i is: 0
//inside for loop i is: 1
//inside for loop i is: 2
//inside for loop i is: 3
//inside for loop i is: 4

console.log(`outside for loop i is: ${i}`);
//output:
//ReferenceError: i is not defined

Due to correctly block scoping our variable, we get the expected behaviour. Another benefit is that we cannot accidentally overwrite a variable that is declared using the let or const syntax. Remember our original example with the studentName variable, if we use the modern ES6 syntax, we get an error:

//Redeclaration. Not Good with Let

let studentName = 'John';
//sometime later in your code, 
//you can accidentally overwrite your variable
//by re-declaring it 
let studentName = 'Jane';

console.log(studentName);
//output 
//SyntaxError: Identifier 'studentName' has already been declared

The keyword let is not the only new way to declare variables. In ES6, you can also declare variables using the const keyword. Using the const keyword we can declare Read-Only Variables in JavaScript. Once a variable is assigned with const, it cannot be reassigned. When using const it is common for developers to use uppercase variable identifiers:

//JavaScript const variables must be assigned a value when they are declared:
const PI = 3.1415

PI = 2;

//TypeError: Assignment to constant variable.

Variables defined with const cannot be Redeclared or Reassigned. Use const when you know that the value should not be changed.

One important point to highlight is that the const declaration protects only the reference to the assigned object; it does not protect the object itself, for example:

You can create a constant object {} or array [] :

const FRUITS = ["Apple", "Banana"];
const STUDENT = {name:"John", age:"20", id:"1234"};

// You can change an element:
FRUITS[0] = "Orange";

// You can add an element:
FRUITS.push("Melon");


// You can change an object property:
STUDENT.name = "Jane";

// You can add a property:
STUDENT.lastName = "Doe";

But you CAN NOT reassign the object or array:

FRUITS =  ["Audi", "BMW", "Chrysler"];  //Error

STUDENT = {car:"Audi", color:"red"};    // ERROR

If you need to protect the whole object, then you can use the Object.freeze() method but be warned this is only a shallow freeze and will not work for nested objects or arrays.


globalThis

In ES2020 a new keyword was added, the globalThis and as the name implies it is literally a Global this Although we already have the window keyword to access the Global objects, historically, accessing the global object has required different syntax in different JavaScript environments.

On the web you can use window, self, or frames - but in Web Workers only self will work. Also in Node.js none of these work, and you must instead use global.

This difference is syntax to access global environment variables was confusing and to solve it the globalThis syntax was introduced to standardise accessing the global this value.

In this way, you can access the Global Object in a consistent manner without having to know which environment the code is being executed.


References