Understanding var and let

Let's first discuss how variables declared, withvar and let are different.

Both let and var are used to declare variables, that can be optionally initialized at the time of declaration.

As per MDN:

The var statement declares a function-scoped or globally-scoped variable, optionally initializing it to a value.

The let statement declares a block-scoped local variable, optionally initializing it to a value.

But there are considerable differences in terms of their creation and their usage.

Parsing a JavaScript program is a two-step process.

Two-step process for variables with var

  1. Creation Step — This step looks for each variable and function declarations in a JavaScript file and creates a memory for that in a global or functional execution context. e.g.
1| console.log(a); // undefined
2| var a = 10;

As I mentioned above, while parsing the above program, the Creational Step will look for all variable declarations and ignores all other statements. Therefore a memory for variable a will be created and will be assigned to undefined by default.

2. Execution Step — This step evaluates every statement line by line and executes the program.

For the example above, line 1 would try to find variable a in global execution context and would try to print its value, which is undefined at that time.

This behavior is also known as hoisting, in which a variable can be used even before its declared.

Two-step process for variables with let

  1. Creation Step — Similar to creation steps for var, memory is created for let statements too, but in this case, memory is not assigned to undefined by default. Instead, the variable goes into a temporal dead zone(TDZ), which means the variable can not be used before it is declared.
1| console.log(a); // Refernce Error
2| let a = 10;

2. Execution Step — At line number 1, the code will now give Reference Error, which is, variable a cannot be used before its declaration(due to TDZ).

Let’s understand how var and let variables are different in their scopes with examples.

Global Object Pollution

var a = 10;
console.log(a); // 10
console.log(window.a); // a

This means that, when a variable is created in a global context(with var), it pollutes the global window object.

let a = 10;
console.log(a); // 10
console.log(window.a); // undefined

But variables created with let in a global context do not pollute the global window object.

Redeclaring Variables

We can redeclare var variables in the same execution context but cannot do it with let variables.

var a = 10;
var a = 20; // works fine
let b = 10;
let b = 20; // Error

Function Scope Vs. Block Scope

function a() {
var b = 10;
if (b > 0) {
var b = 20; // same variables
}
console.log(x); // 20
}
function z() {
var x = 10;
if (x > 0) {
let x = 20; // different variables
}
console.log(x); // 10
}

Let’s first understand closure. As per MDN

A closure is the combination of a function bundled together (enclosed) with references to its surrounding state (the lexical environment). In other words, a closure gives you access to an outer function’s scope from an inner function. In JavaScript, closures are created every time a function is created, at function creation time.

Closures are useful because they let you associate data (the lexical environment) with a function that operates on that data. This has obvious parallels to object-oriented programming, where objects allow you to associate data (the object’s properties) with one or more methods.

function outer() {
var a = 10;
return function inner() {
return a + 10;
}
}
var inner = outer();
console.log(inner()); // 20

Closures are also very helpful in implementing modular JavaScript. An IIFE (Immediately Invoked Function Expression) can be beneficial in that regard.

var App = {};(function(){
var loggedInUser = {
name: "John Doe",
email: "john@example.com",
password: "somepassword",
token: "sometoken",
}
function getLoggedInUser() {
var user = { ...loggedInUser };
delete user.password;
return user;
}
function updateToken(token) {
loggedInUser = {...loggedInUser, token};
}
App.auth = {
getLoggedInUser,
updateToken,
}
})()
console.log(App.auth.getLoggedInUser()); // Works Fine
console.log(getLoggedInUser()); // Reference Error

But when I looked at it first, I wondered why we have to use one extra anonymous function and call it immediately. But since JavaScript didn’t have classes and namespaces before ES2015, this was the only pattern to create modules(Creating private state and implementations and providing only public APIs which will be used in other modules).

Now, why IIFEs, since to create a state or private properties/variables, we had var(before ES2015), and var is functional scope variable, we had no other way than to wrap that state and methods, in an outer function. And since we want to run it once, the outer function should be anonymous and called after its declaration.

Now, let's try to implement the same module using just blocks.

let App = {};{
let loggedInUser = {
name: "John Doe",
email: "john@example.com",
password: "somepassword",
token: "sometoken",
}
let getLoggedInUser = function() {
var user = { ...loggedInUser };
delete user.password;
return user;
}
let updateToken = function(token) {
loggedInUser = {...loggedInUser, token};
}
App.auth = {
getLoggedInUser,
updateToken,
}
}
console.log(App.auth.getLoggedInUser()); // Works Fine
console.log(getLoggedInUser()); // Reference Error

Great!! We now got rid of the anonymous wrapper function and the function call. Since let is a block scoped variable, we just need to wrap all private variables and methods inside a block.

I personally have been asked this question which has var-forloop, and inside the for loop block, there is a setTimeout which prints the variable i. Let’s check this below.

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

If you try to run the above code snippet you will get 3 times 3, and not 0, 1 and 2, which you might have expected.

Let’s find out how var works here.

  • Since var i is not a block scoped variable, i will be created in global scope, if you print window.i you will get 3(which is expected).
  • Since the inner function which is passed as a callback in setTimeout, with every loop a new callback is registered and is lexically binded to i variable which has been declared in global context.
  • Since all three callbacks are binded to same window.i lexically, which is 3, when callbacks are called. Everytime 3 is printed.

Now what could be a solution for this problem?

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

Convert var i into let i, and you will get 0, 1, and 2 as expected. But how changing just var into let works fine for this case.

Let’s find out how let works here.

  • With each loop, a new block is created and let i created for each block.
  • Now inside this block, the callback is lixically binded to let i, which would be 0, 1, and 2.
  • Therefore callbacks run, we get 0, 1, and 2.

But how to make the code work even without changing let from var? Since var is a functional scoped variable, we would need to wrap setTimeout inside a function which will get new value on each loop and callback will be binded to the functional scoped variable instead of a global variable.

for(let i=0; i<3; i++) {
(function(a){
setTimeout(() => console.log(a), 1000);
})(i)
}

I hope you enjoyed reading. Thank You!!!

Sr. JavaScript Developer at GlobalLogic, React, Nextjs, GraphQl, Serverless, JamStack