Variables in ES6: Let, Const, and Var

Anton Ioffe - August 30th 2023 - 17 minutes read

I. Introduction

Before diving into any programming language or framework, understanding the fundamental building blocks is crucial, and JavaScript is no exception. Among these foundational elements, variables play a critical role. In JavaScript, we have three different ways to declare variables: let, const, and var. Each of these declarations has its own uniqueness and carries a different significance.

The var keyword has been there since the inception of JavaScript and was the sole way to declare variables until let and const were introduced in ES6 (ECMAScript 2015). All three provide the capability of holding value and they can be assigned and re-assigned values. Understanding when and how to use them can help write more robust and less error-prone code.

let provides block-level scoping, meaning a new scope is created between a set of curly braces {}. This benefit is not available with var which has a function-level scope, making it accessible outside its immediate block.

On the other hand, const behaves in a similar manner to let in terms of scope, but as the name suggests, once a 'constant' value is assigned, it cannot be re-assigned. This feature makes const especially suitable for situations where certain values are not intended to be changed once they are set.

This understanding of var, let, and const will ensure more readability and maintainability in your code. It is also a stepping stone towards understanding more complex JavaScript concepts such as hoisting and closures. In upcoming sections, we will explore each of these keyword usages in-depth, analyze their strengths and weaknesses, and learn to use them correctly in our code.

Stay tuned as we go beyond the basics and step into the nuanced world of JavaScript and TypeScript.

II. Understanding Var, Let, and Const in JavaScript: Definition and Examples

The JavaScript language provides us with three distinct ways to declare a variable: var, let, and const.

First, let's understand var. The var keyword was the traditional way to declare variables in JavaScript. When you declare a variable using var, you can define it anywhere in the program, and then re-assign or modify it at any point.

Here is a basic example of var:

var name = 'John Doe';
console.log(name); // Displays 'John Doe'

name = 'Jane Doe';
console.log(name); // Now displays 'Jane Doe'

Next, we have let. The let keyword is a modern addition to JavaScript and is now considered the standard way to declare a variable. Similar to var, you can re-assign or modify a let variable at any time. However, let has block-level scope, meaning the variable only exists within the block of code in which it is declared.

Here is a basic example of let:

if (true) {
    let city = 'New York';
    console.log(city); // Displays 'New York'
}

console.log(city); // Returns an error, city is not defined 

In the example above, city only exists within the if statement. If you try to call city outside the if block, you will get an error because city does not exist outside that block.

Lastly, we have the const keyword. This is used to declare a constant, an unchangeable or read-only reference to a value. Once a const has been declared and assigned a value, you cannot change this value.

Here is a basic example of const:

const planet = 'Earth';
console.log(planet); // Displays 'Earth'

planet = 'Mars'; 
//Returns an error, you can't reassign a const variable

In conclusion, the three keywords var, let, and const offer different variable declaration options in JavaScript. We can use var to declare a variable with global or function scope, while the let keyword gives a block-level scope. The const keyword, on the other hand, enables us to declare constants or read-only references to a value. Understanding these differences is crucial for efficient and effective JavaScript programming.

III. Comparisons Between var, let, and Const

As JavaScript developers, we often encounter var, let, and const. It's important to understand the differences between these three variable declarations as it has significant implications for how we structure and write our code.

Var vs. Let: The Old and the New

Var has been a staple of JavaScript since it was first created. However, with ECMAScript 6 (ES6), let and const were introduced as alternative ways to declare variables, bringing some changes in terms of scope and hoisting.

One of the most significant differences between var and let is scope. Var is function scoped, meaning it's contained within the function where it is declared. On the other hand, let is block-scoped, meaning it's limited to the block, statement, or expression it is declared in.

Secondly, hoisting behaves differently for var and let. When JavaScript code is compiled, variable and function declarations are moved to the top of their containing scope. This is known as hoisting. However, while var declarations are initialized with undefined, let variables are not initialized. Trying to access a let variable before it's declared will result in a Reference Error.

Let's illustrate with some code:

console.log(myVar); // Output: undefined
var myVar = 5;

console.log(myLet); // Output: Error - myLet is not defined
let myLet = 5;

Let vs Const: The Flexible and the Immutable

Drawing a comparison between let and const, the most distinct difference is mutability. A let declared variable can be reassigned, while a const variable cannot.

This would generate an error:

const myConst = 'Hello, world!';
myConst = 'Goodbye, world!'; // Error - Assignment to constant variable.

Here it must be clarified that 'const' does not make the variable itself immutable, just the assignment. If you assigned an object or an array to a const variable, you could still mutate its properties or elements. The variable itself just can't be reassigned.

Var vs Const: The Old and the Immutable

Comparing var to const, we see a similar difference as with let, that is, scoping and hoisting. Var has function scoping and is hoisted, while const has block scoping and is not hoisted. Additionally, var variables can be reassigned, while const cannot be.

Does 'var' Still Have a Place?

Given these comparisons, it's worth asking if var still has a place in modern JavaScript. With let and const, we have more control over scope and can have a clearer intention through our code's immutability. In general, it is considered good practice to prefer let and const over var. However, knowing how var behaves becomes valuable when dealing with legacy code or implementing feature detection for older browsers.

To demonstrate when to use let vs const, consider asking yourself: "Will I need to reassign this variable?" If the answer is no, use const. This communicates to other developers that the value should not change over time. Use let when you anticipate the variable will need to change, such as in a for loop.

Conclusion

Understanding the differences between var, let, and const helps us write cleaner, more predictable code and avoid common bugs related to scope and hoisting. It's highly recommended you use let and const in modern JavaScript and reserve var for cases when backwards compatibility is essential. The choice between let and const depends on whether you need to reassign the variable or not. In due course, mastering and using these declarations properly will lead to robust and reliable code.

IV. Variable Declaration In Different Scenarios

Declaring a variable without using any keyword can seemingly work, but can lead to potentially risky behavior. For instance, consider the following example:

myVariable = 'Hello, World!';
console.log(myVariable); // This will print "Hello, World!"

At first, doing this might seem harmless, but it can cause issues. The variable myVariable has become a global value, that is, it is attached to the global window object.

In real-life coding, when working with larger codebases, using such undeclared variables becomes a potential hazard. It can lead to namespace clashes and unpredictable behavior, hence is ill-advised.

Fortunately, the ES6 update in JavaScript introduced var, let, and const for variable declaration, allowing us to prevent such problems:

var globalValue = 'Hello, World!';
let scopeVariable = 'Hello Again, World!';
const constantVariable = 'Hello One More Time, World!';

The keyword var is used to declare a variable that can be accessible globally or within the scope of a function, depending upon its declaration. Its usage can potentially introduce bugs due to variable hoisting.

The let keyword was introduced as a solution to problems that come with hoisting. A variable declared with let is bound to its block scope and is only accessible within the nearest set of curly braces, be it a function, if-else block, or for-loop block.

Consider this example:

if (true) {
    let x = 5;
}
// trying to access x here will throw a ReferenceError
console.log(x);

In the code snippets above, x is not accessible outside the if block. Attempting to access it thereafter results in a ReferenceError.

For scenarios where a global data is essential, ES6 offers a cautious path:

let globalData = 'I am global!';

In this example, globalData is a global variable and can be accessed throughout your code. Nevertheless, it is best to avoid declaring global variables to prevent namespace pollution and irregularities.

The let and const declarations behave differently when compared to var. These have block scoping, where JavaScript engine stores these variables in a lexical environment. This feature helps to avoid many bugs that are common with var declarations.

Suppose we have two developers working on the same project and both declare a var variable with the same name. When the application runs, it will lead to a clash as each var variable will overwrite the other. However, if they use let instead, such a collision can be avoided because JavaScript will keep track of their block scopes separately.

Let's look at an example that shows a common mistake when using var:

var x = 1;

function myFunction() {
    var x = 2;
    console.log(x); // This will print 2 
}

myFunction();
console.log(x); // This will still print 1 

In this case, the variable x declared inside myFunction() does not overwrite the global variable x. However, if we declare x without var keyword inside the function, it will overwrite the global variable:

var x = 1;

function myFunction() {
    x = 2;
    console.log(x); // This will print 2 
}

myFunction();
console.log(x); // This will now print 2!

This behavior can lead to various bugs in your application if you are not cautious.

In conclusion, when declaring variables in JavaScript, it is recommended to use the let keyword for variables that will change over time, and const for those whose value will not alter once assigned. Always avoid declaring global variables whenever possible, especially using declaration keywords, to sidestep potential issues down the line.

V. Understanding Const: Mutability and Use Cases

The const keyword in JavaScript is often misunderstood, particularly when it comes to handling mutability. While using const to declare a variable signals that the identifier cannot be reassigned, this does not mean that the value it holds is immutable.

The Illusion of Immutability

Let's start with a simple case.

const greeting = 'Hello, World!';
greeting = 'Hi, Universe!'; // Uncaught TypeError: Assignment to constant variable.

In the example above, trying to reassign a const variable will throw an error, illustrating that const does not allow reassignment. Now, let's say we've got an object or an array:

const myArray = [1, 2, 3];
myArray.push(4); // This works!

const myObject = {a: 'one', b: 'two'};
myObject.c = 'three'; // This works too!

Even though myArray and myObject are declared with const, we can modify them. This is because const in JavaScript means constant reference, not constant value. For primitive values (like strings, numbers, booleans, etc.), const behaves as a true constant, giving us an unmodifiable value. For complex types (like objects, arrays, or functions), const does not prevent the mutation of properties because the reference to the object is what cannot be changed.

Mindful Use of Const

The possibility of mutating const objects leads to some caveats in using const indiscriminately. It might seem like a good idea to declare every variable as const as a means of writing more reliable code. However, const does not guarantee a variable's immutability, which can lead to misunderstandings between developers, particularly when working on a shared project.

Moreover, using const for everything could mean signaling that a value should never be reassigned even when it should be. It's important to remember that we have let and var for a reason. Sometimes, changing a variable's value is exactly what our code needs to do.

The practice of favoring const should be made in a context of weighing its actual benefits, the main one being communicating to other programmers that a variable shouldn't (and literally can't) be reassigned. If that’s not the goal, using let or var may make the intention behind the code clearer.

Const and Objects

When declaring objects, const is particularly useful for preventing accidental reassignment. However, the properties within the object remain mutable.

const myObject = {a: 'one', b: 'two'};
myObject = {a: 'three'}; // Uncaught TypeError: Assignment to constant variable.
myObject.a = 'three'; // This works!

As shown above, const can't prevent property mutations within objects. If you want to make an object truly immutable, you would need to use Object.freeze(myObject). This will prevent any changes, including the addition, modification, or deletion of properties.

In conclusion, using const can contribute to more predictable, and potentially more readable, code, but it should be used thoughtfully. Remember that const only guarantees a constant reference, not a constant value, and does not make an object's properties immutable.

VI. Special Cases and Considerations

Even seasoned JavaScript developers sometimes get into heated debates, discussing whether using var is a bad practice or just another myth bubbling up from misunderstood concepts. This special case of variable declaration deserves a closer look.

var, as most know, is function-scoped which can lead to unexpected outcomes, especially for developers coming from block-scoped languages. This lack of block scoping is the main reason it's often seen as a bad practice.

However, it isn't that simple. The var keyword does have its unique behavior and it can be beneficial in specific scenarios where function scoping is required. Understanding what it can offer and the quirks around it can indeed turn var into a powerful tool, if used wisely and specifically.

Now contrast this with const. Const declaration in JavaScript is block-scoped and its value cannot be changed once assigned. This offers an apparent benefit - if a variable isn't meant to change during the code's lifecycle, defining it as const alerts other developers about the intended non-mutability of the variable.

However, remember that const does not make the assigned value itself immutable, it just prevents re-assignment. This is a common misunderstanding leading to mistakes. It can pose a problem while using objects or arrays - as properties can be modified even though the declared variable is const.

As such, const is preferred in cases where re-assignment is not required to avoid accidental re-assignment. For functions, using var is generally avoided due to possible redeclarations and hoisting, while const and let are preferred due to their block scope and temporal dead zone properties.

Switching gears, let's talk about static. Now, this can be confusing because static isn't a way of declaring variables in JavaScript like var, let, or const. It's used within classes to declare static methods or properties that belong to the class itself and not the instances.

The use of static is quite different from const - const is about variable declaration and static is about method/property attachment. Static creates properties or methods available on the class itself, while const declares a variable that cannot be reassigned.

Addressing invalid variable scenarios in JavaScript: carried over from its early days for being a rather lenient language, JavaScript can sometimes be too forgiving when encountering dubious code. For instance, due to hoisting, JavaScript won't throw an error for using a variable before it's declared.

console.log(myVar); // undefined, not an error 
var myVar = 5;

This is known as hoisting and it's a common pitfall for developers unaware of this specific feature. Hoisting lets you access variables before they're declared due to variable declarations (not initializations) being moved up to the nearest enclosing scope. JavaScript moves up all the variable declarations to the top, but leaves their initializations in place, leading to undefined. This doesn't occur with let and const - trying to access them before declaration will throw a `ReferenceError.

To summarize, each variable declaration method - var, const, and static have their particular use cases, benefits, and drawbacks. Knowing these can highly assist a developer in writing more predictable and readable code.

VII. The Phasing Out of Var in ES6 and Beyond

In the evolution of JavaScript, one of the most impactful updates was the introduction of let and const in ECMAScript 2015 (ES6). These new variable declaration keywords brought to light the limitations of var. Since then, var has been gradually phased out in modern JavaScript and TypeScript code patterns, and for several good reasons.

The launch of let and const has shed light on some inherent shortcomings of var. Let's break it down:

1. Scope confusion:

Probably the most profound reason why var has lost its charm is its scoping rules. Unlike most other programming languages, var does not have block-level scope but function-level scope. This can lead to confusion, especially for developers coming from a language like Java or C++, which respects block-level scoping.

Consider this var example:

function varExample() {
    var foo = 'hello';
    if(true) {
        var foo = 'goodbye';
    }
    console.log(foo); // Outputs: 'goodbye'
}

A developer with a background in C++ or Java would expect the output to be 'hello', but in JavaScript, the output is 'goodbye'. This is because var is not blocked scoped, but function scoped, leading to unintentional re-declaration.

2. Hoisting hitches:

Another downfall of var is hoisting. In JavaScript, variable and function declarations are hoisted to the top of their containing scope. However, with var, only the declaration part is hoisted and not the initialization. This could lead to unexpected outputs when a variable is used before its declaration.

Take this example for instance:

console.log(foo); // Outputs: undefined
var foo = 'hello';

Here, var foo is hoisted to the top of its scope, but its value 'hello' is not. So on attempting to log foo before it has been assigned 'hello', the output is undefined rather than a ReferenceError.

3. Global namespace pollution:

Another significant disadvantage of var is that it could pollute the global namespace. Since var has a wider scope, there's a greater risk of unintentionally reusing variable names, thus overwriting existing values.

var foo = 'hello';
...
var foo = 'goodbye'; // Overwrites the previous 'foo'

On the other hand, let and const live in the block where they are declared and cannot be accessed outside that block. This prevents unintended value changes, thus making the code safer and more predictable.

4. Absent temporal dead zone:

Unlike let and const, var doesn't have a temporal dead zone (TDZ). TDZ refers to the period wherein a variable is unaccessible until its declaration. var variables can be accessed before declaration albeit with a value of undefined. This can cause confusion for developers and lead to possible bugs.

To conclude, the phasing out of var in ES6 and beyond was to address these inherent challenges and provide developers with a safer, more predictable coding environment. let and const offer block-level scope, controlled hoisting, immunity from namespace pollution, and a proper TDZ.

On your upcoming challenge, try to refactor a piece of code which extensively uses var, replacing it with let or const and note your observations.

VIII. Summary

Throughout this article, we've extensively studied advanced concepts of Javascript and Typescript, comparing various techniques and their impacts on performance, memory management, code complexity and readability, modularity, and reusability.

We pointed out common errors developers often encounter and how to skillfully avoid them, thereby enhancing the efficiency and effectiveness of your code, making it stand out.

Consistent growth as a developer necessitates good coding habits. One of these habits is consistently evaluating the quality of your code and implementing best practices to continually raise the standard.

Now, here comes your challenge. Conceive a functional feature using a web development framework that extensively utilizes the Javascript and Typescript concepts explored in this article.

Let's narrow down the task for you: Build a multiplayer game that users can join, leave and interact in real-time. Utilize the advanced concepts, learnt thus far, optimally for performance and low memory footprint. The solution would require careful consideration of the optimal design patterns, memory management, and event-driven programming.

Don't just utilize concepts blindly, remember to always analyze the 'why' behind choosing a particular technique over the other.

Keep in mind that there's no such thing as 'perfect code,' but striving towards 'better code' each time is what sets you apart. As you adopt new strategies, your understanding expands and your code improves, thus make it a point to stay open and inquisitive.

Writing code is like crafting a masterpiece, where you're the architect. Happy Coding!

Here's an example of how one such problem can be tackled:

// Creating a class for the game
class MultiplayerGame {
  constructor() {
    this.players = [];
  }

  // Method to add a player
  addPlayer(player) {
    this.players.push(player);
    console.log(`${player} joined the game.`);
  }

  // Method to remove a player
  removePlayer(player) {
    this.players = this.players.filter(p => p !== player);
    console.log(`${player} left the game.`);
  }
}

// Instance of the game
const game = new MultiplayerGame();

// Add and remove players
game.addPlayer('Player1');
game.addPlayer('Player2');
game.removePlayer('Player1');

This snippet encapsulates our defined concepts and provides a real-world example of a multiplayer game where players can join and leave.

Don't Get Left Behind:
The Top 5 Career-Ending Mistakes Software Developers Make
FREE Cheat Sheet for Software Developers