JavaScript: var, let and const- should you stop using var?

If you are familiar with JavaScript, you may already know the var keyword. This keyword was used for ages for declaring JavaScript variables. However, in 2015, ECMAScript, which is a JavaScript standard, introduced two new ways to declare identifiers- let and const.

In this article, I try to discuss the differences between var, let and const and if you should stop using var altogether.

Hoisting

Before we get into the details of the difference between these, it is essential to know of a JS mechanism called Hoisting. JavaScript lets the user declare a variable anywhere in its body. However, internally JavaScript performs an action called Hoisting through which the declarations anywhere within a particular scope are resolved at the beginning of the scope.

As the JavaScript guru Kyle Simpson says in his article,

It's [Hoisting] not exactly a technical description for how it works, but more a metaphor

For example, consider the code snippet below:

console.log('Hello World');
var myName = 'Aswin';

Before the code is executed by a JavaScript engine, JavaScript resolves the declarations in such a way that the resulting flow almost looks like as follows:

var myName;
console.log('Hello World');
myName = 'Aswin';

In the above example, the variable is declared in a global scope. Had it been declared within a particular scope, the hoisting would have differed accordingly.

Now with hoisting in mind, let us get to how var, let and const differ from each other.

Declaration using 'var'

Scope

When a variable is declared with the var keyword, the variable declaration is defined with global scope or function scope based on where it is declared. Even if the variable is declared inside a block, like a for loop or an if condition, the variables are still hoisted outside the block.

For example, consider the code below:

function greet(gender) {
  if (gender == 'female') {
  	var name = 'Nalina';
  } else {
  	var name = 'Aswin';
  }
  console.log('Hello ' + name);
}

greet('female');

As per common programming logic, since the declaration var name = 'Nalina' is defined within an if block, the variable must exist only inside the block. The execution of the code should have resulted in an error according to conventional logic since the variable name is not defined within the scope of the line console.log('Hello ' + name).

However the output of this JavaScript code would be:

Hello Nalina

This is because the variable declaration is hoisted to the top of the function scope irrespective of the block in which it appears. The resultant program flow would be similar to:

function greet(gender) {
	var name; // hoisting equivalent

	if (gender == 'female') {
		name = 'Nalina';
	} else {
		name = 'Aswin';
	}
	console.log('Hello ' + name);
}

Initialisation of the variable

When declaring the variable using var keyword, the variable is hoisted and initialised with undefined as the value. Because of this, JavaScript lets the developers to use the variable even before it is declared.

For example, consider the code segment below:

myVar = 9;
var myVar;
console.log('The value is ' + myVar);

Execution of this code results in the following output:

The value is 9

This is as a result of hoisting. The code snippet specified above is processed by JavaScript to look as follows:

var myVar; // initialised as myVar = undefined;
myVar = 9;
console.log('The value is ' + myVar);

Now you may be able to appreciate how the code execution resulted in the output mentioned above.

Redeclaration of variables

Most programming languages do not allow to redeclare variables within the same scope. However, JavaScript supports variable re-declaration using the var keyword even within the same scope. This has turned out to be more of a mess than benefit in real life.

For example,

function doSomething() {
	// some long block of code
	var i = 100;
	// some other long block of code
	var i = 10; // unintentionally re-declared
	// do something more
}

As shown in the code snippet above, developers usually declares variables without being aware that it is already declared earlier in the code. While programming in JavaScript, this results in ambiguous results rather than straight-up throwing an error.

Declaration using 'let'

Scope

As in the case with 'var', variables can be declared using 'let' as well. When using let, the variable is block scoped. A block is the region that is surrounded by { }. During hoisting, a variable declaration using the let keyword is hoisted to the top of the block unlike the 'var' declaration which hoists to the top of the function (or global).

Let's consider the earlier example if declared using let:

function greet(gender) {
  if (gender == 'female') {
  	let name = 'Nalina';
  } else {
  	let name = 'Aswin';
  }
  console.log('Hello ' + name);
}

greet('female');

This code results in an error as opposed to the earlier case.

Uncaught ReferenceError: name is not defined

To understand why, we can check how the program flow would look like after hoisting:

function greet(gender) {
  if (gender == 'female') {
    let name;
  	name = 'Nalina';
  } else {
    let name;
  	name = 'Aswin';
  }

  console.log('Hello ' + name); // name is not defined.
}

greet('female');

As you can see, the identifier name is not defined and hence could not be referred in the console log. Recall that when declared using the var keyword, this worked because the variable declaration was hoisted to to the top of the function scope.

Initialisation of the variable

Another thing to be noted while using let is that the initialisation does not take place along with hoisting. Rather the value is initialised only at the line where the variable was originally declared.

Lets again consider the example that we took while explaining initialisation using var:

myVar = 9;
let myVar;
console.log('The value is ' + myVar);

After hoisting the program flow is made to look like:

let myVar;
myVar = 9;
myVar = undefined; // note that initialisation happens at the line where it was declared
console.log('The value is ' + myVar);

Hence the output would be:

The value is undefined

Redeclaration of variables

As opposed to using var for declaring variables, declaring with let does not allow redeclaration within the same scope. To illustrate this let's take an example:

function doSomething() {
	// some long block of code

	let i = 100;
    console.log('The value of i is ' + i);

	// some other long block of code

	let i = 10; // unintentionally re-declared
    console.log('The value of i is ' + i);

	// do something more
}

In the example above, the output would be:

Uncaught SyntaxError: Identifier 'i' has already been declared

Yes, the code didn't even start running. Redeclaration of variable that were originally declared using let is flagged even before executing the code.

let myVar = 10
var myVar = 11

As you might have guessed, the code above will also fail before execution. In fact, if a variable is declared using let, it cannot be overridden even by using var.

However, redeclaring a variable in a different scope is perfectly fine. For example, consider the code snippet below that would correctly execute:

let myName = 'Aswin';
if (myName == 'Aswin') {
  let myName = 'Nalina';
  console.log('My name inside the block: ' + myName);
}

console.log('My name in outside the block: ' + myName);
  

The program would run fine and would provide the following output:

My name inside the block: Nalina
My name outside the block: Aswin

This is because the let declarations have a block scope. Hence the initial declaration is applicable in the entire block, however it is overridden by the second declaration inside the block. The value hence will be 'Nalina' within the block, however remains to be 'Aswin' outside the block.

Declaration using const

The const keyword works exactly like a let keyword when it comes to the scope, initialisation and redeclaration. However there is a key difference - once defined, const doesn't let its value be changed!

When declaring with a const keyword, it is mandatory to provide the value. A declaration without a value would result in an error as explained with the example below:

const pi;

Output:

Uncaught SyntaxError: Missing initializer in const declaration

Also, once declared the value is not allowed to be changed, and attempting to do so would again result in an error. Again, I'll explain with an example:

const pi = 3.14;
pi = 3.14159;

The code provided above results in an error as shown below:

Uncaught TypeError: Assignment to constant variable.

Const and Immutability

I mentioned that once a constant is defined, its value cannot be modified. However, this does not mean that constants are immutable. If an object is declared using the const keyword, a different object cannot be assigned to the identifier. However, the contents of the object could well be updated.

For example,

const user = {
	name: 'Aswin',
	company: 'TCS Research'
}

user.company = 'TATA Consultancy Services';

As you can see in the code segment above, the company attribute of the author object can be modified although the identifier is declared using const.

The problem with var

As we have seen through various examples, var is a little different when it comes to usage and is mostly somewhat ambiguous. I would summarise the differences through a few key points:

  • Using let or const to define variables would keep the variables block scoped. However when using var, the variable is global/function scoped, which might be confusing for people coming from Java/C++ backgrounds.
  • Using var lets the variable be redeclared within the same scope. While this may seem like a good-to-have capability, I think this often create ambiguous results and consume a lot of time in debugging.
  • The variable declarations are hoisted in JavaScript- pulled to the top of their corresponding scopes. However, var is initialised with undefined as the value, while the initialisation takes place only at the original lines in case of let and const.

While I truly believe that usage of var is better avoided whenever possible, there is a significant community of advocates (including Kyle Simpson) of continued usage of var.

Notably Kyle Simpson argues that usage of let could result in Temporal Dead Zones (TDZ) in which a variable declared with 'let' if used before the declaration would result in an error. However, with my limited experience with Object-Oriented programming languages like Java and C#, this is more of a safety check than a disaster. And there could be numerous others that he discusses in an article, but I still feel that var is much more confusing that the let, const pair.

Conclusion

So as we have discussed through various examples, a variable in JavaScript (ES6) can be declared using the keywords - var, let and const. However, each of these are different and applicable in altogether different scenarios. The major differences between these types of declarations are in the scope, initialisation and re-declaration of the variables.

When working on ES6+ compatible environments, I think it's really a good idea to avoid using var. Using let and const would yield much more consistent and unambiguous results, at least that's what I could infer from my personal experience. However, since there are many notable individuals on both sides of the argument, it might be a subjective call to make. Perhaps reading through a few of the notable opinions on this would shed some light if you are confused.

EDIT: The original article was a bit stronger in recommendation to avoid the use of var. However, the responses that I received on various forums about this article made me rethink, particularly about the subjective nature of the topic. As I mentioned, I still believe it is for the greater good to avoid using var, but again, it could vary from person to person based on their programming background and beliefs.

No comments

Post a Comment