Introduction
Programming has always included the definition of data, manipulation of data, and finally displaying data. Data can be represented as bits of information that we can alter in computer programs. Since memory locations aren't very human-readable, and change through time - we've started annotating variable data, with human-readable signifiers, which we can call on, to indirectly point to data in-memory.
These signifiers are commonly called variables or reference variables.
Variables are, essentially, pointers or references to some data in a machine's memory, and the pointer can dynamically be changed to reflect the true state of the data we've "labeled".
Note: Commonly and colloquially, it's said that "variables store data" and that they're "containers for data". This is technically incorrect, and stems from a blurred semantic boundary - it's unclear whether people refer to reference variables or objects in-memory. (Reference) Variables are pointers, and they point to objects in the machine's memory - where the data is stored. The colloquial terms are commonplace enough that you'll find them present in documentation, but it's worth keeping object-memory-allocation at least in the back of your head.
Prior to the release of ES2015 (ES6), JavaScript variables were only declared using the var
keyword; however, with the introduction of ES6, new ways to declare variables, let
and const
, were introduced. This oftentimes brings up questions - mainly as to which keyword should be used, and when:
var english = "Hello there!";
let french = "Bonjour!";
const german = "Hallo!";
In this guide, we will explore the difference between the three various ways to declare variables in JavaScript -
var
,let
andconst
, their scopes and when to choose which.
What is Scope in JavaScript?
Scope is an important concept to grasp in order to write code in most programming languages, and plays an important part in choosing which variable keyword you'll want to use. Scope defines variable availability. In JavaScript, we have two scopes: global and local.
- Global Scope: Variables declared outside any code block or function are known as global variables because they have a global scope, and can be referenced from any function or block.
Note: In a JavaScript document, only one global scope exists.
Suppose you have a script file. Again, any variable declared outside any function or block is globally scoped:
// Initialized outside of function or block
var name = "John Doe";
function logName() {
console.log(name);
};
logName();
In the example above, name
is accessible within the logName()
function, as it has a global scope. It exists in the context of the application, and the logName()
function can call on that context!
- Local Scope: Variables declared within any code block or function are known as local variables, because they have a local scope. They can be referenced only within the code blocks or functions in which they're defined.
function logName() {
// Initialized within a function or block
var name = "John Doe";
var id = 1;
console.log(name);
};
function logId() {
console.log(id);
}
logId();
This results in:
error: Uncaught ReferenceError: id is not defined
How come? id
is defined - but it isn't defined in the scope of the logId()
function. As far as the function is concerned - no id
exists. It starts by checking whether there's a locally scoped variable. Since there's none, it checks whether there's a globally scoped variable. If not - id
is not defined from the context of logId()
!
With the primer/reminder out of the way - let's take a look at how var
, let
and const
depend on the scope, and when each should be used!
The var Keyword in JavaScript
In JavaScript, var
is a reserved keyword which is followed by a reference variable name. The name defined after the keyword can then be used as a pointer to the data in-memory.
Using var
is the oldest method of variable declaration in JavaScript. Let's declare a variable and initialize it by assigning a value to it using the assignment operator (=
):
// Declaration and initialization
var name = "John Doe";
Alternatively, you can break this down into two steps - variable declaration (what it is), and variable initialization (assigning a value to it):
// Declaration
var name;
// Initialization
name = "John Doe";
Note: In strongly-typed languages, such as Java, for a long time, you'd define the type of the variable during declaration, and during initialization, you could only assign a value fitting that type. Since Java 10 - a var
keyword has been added, which is type-agnostic and infers the type during runtime.
Scope of var
When defined within a function - any var
is restricted to that function. When defined outside of a function, a var
is global:
Check out our hands-on, practical guide to learning Git, with best-practices, industry-accepted standards, and included cheat sheet. Stop Googling Git commands and actually learn it!
var firstName = "John";
function checkLastName() {
var lastName = "Doe";
}
We have two declarations in the preceding example: firstName
is globally scoped because it's defined outside a function, and lastName
is locally/function scoped because it's defined within a function:
var firstName = "John";
function checkLastName() {
var lastName = "Doe";
console.log(lastName); // "Doe"
console.log(firstName); // "John"
}
checkLastName();
console.log(lastName); // Uncaught ReferenceError: lastName is not defined
So far so good. However - var
has an issue.
The Issue with var
var
is not block-scoped. When you declare a variable within a code block, using curly braces ({}
), its scope "flows out" of the block! For instance:
var name = "John Doe";
var someBool = true;
if (someBool) {
var name = "Daniel Joan";
}
console.log(name);
The name
that points to "John Doe" is global, and the name
that points to "Daniel Joan" is defined within a block. However, when we try printing the name
that's within scope, we run into:
Daniel Joan
var
is not block-scoped. We may think that we've defined a localvar name
to point to "Daniel Joan", but what we've done in reality is overwrite thevar name
that points to "John Doe".
Declaring variables using the var
declarations everywhere in your code can lead to confusion, overwriting of existing global variables and by extension - bugs, just as we saw in the code snippet.
This is where let
and const
kick in!
The let Keyword in JavaScript
The let
declaration was introduced with ES6 and has since become the preferred method for variable declaration. It is regarded as an improvement over var
declarations and is block-scoped (variables that can be accessed only in the immediate block), circumventing the main issue that can arise with using var
.
Scope of let
A variable defined with the let
keyword has a scope limited to the block or function in which it is defined:
let firstName = "John";
let lastName = "Doe";
let someBool = true;
if(someBool){
let firstName = "Jane";
console.log(firstName);
}
console.log(firstName);
This time around - the firstName
referring to "Jane" and the firstName
referring to "John" don't overlap! The code results in:
Jane
John
The firstName
declared within the block is limited to the block in scope and the one declared outside the block is available globally. Both instances of firstName
are treated as different variable references, since they have different scopes.
The const Keyword in JavaScript
The const
declaration was introduced with ES6, alongside let
, and it is very similar to let
. const
points to data in memory that holds constant values, as the name implies. const
reference variables cannot be reassigned to a different object in memory:
const name = "John";
const name = "Jane";
This results in:
Uncaught SyntaxError: Identifier 'name' has already been declared
Scope of const
The scope of a variable defined with the const
keyword, like the scope of let
declarations, is limited to the block defined by curly braces (a function or a block). The main distinction is that they cannot be updated or re-declared, implying that the value remains constant within the scope:
const name = "John";
name = "Doe";
// Uncaught TypeError: Assignment to constant variable.
Good Coding Conventions
So, what does this all mean, and which should you choose, other than the obvious requirements to avoid bugs? This can actually be boiled down to a couple of good practices:
const
is preferred tolet
, which is preferred tovar
. Avoid usingvar
.let
is preferred toconst
when it's known that the value it points to will change over time.const
is great for global, constant values.- Libraries are typically imported as
const
.
When importing a library and instantiating it - you don't want to be able to reassign the instance to something else, since you'd enter a slippery slope of "using the library", whereas, something else is "slipping code in" under the hood.
For instance, if you were to require()
a library such as Axios, you're conceivably wanting to use its API. However, there's nothing preventing you (or someone else) to switch out the axios
instance with something else if you haven't used const
to declare it:
let axios = require('axios');
axios.get('some_url').then(someFunction());
axios = "Totally not a string!"
axios.get('some_url').then(someFunction()); // String has no method `get()`
By having axios
be const
- this issue is avoided. Additionally, you can define global constants, which can be used as configuration constants:
const WIDTH = 1920;
const HEIGHT = 1080;
Conclusion
In this guide, we've explored the progression of variable declaration in JavaScript, from the original var
to the newer let
and const
.
We've explored Scopes in JavaScript and how the different declaration signifiers affect the scope of a variable in code, noting a glaring issue with using var
. Finally, we've explored some good practices, noting when to use which keyword.