[JS variables] what is var, let, and const?
2023-04-08
In any script/programming language, variable is a logical storage where you can assign value into, and JS has three types of variables; var
, let
, and const
.
Let’s get familiar with some of the variable-related terms first.
- Declaration is a variable creation without any value in it.
- Initialization is to assign value into a variable declared.
- Scope is an extent of the area a variable can be used.
- Global scoped variable declared outside any (function) block {} can be accessed from anywhere.
- Block scoped variable can only be accessed within a block {} it’s declared, and it cannot be accessed outside the block.
To verify the scope & behavior of JS variables, we will be using console.log();
a JS method that outputs a message to the (web) console.
What is var
?
var
is the oldest variable in JS.
The scope of var
can be either global or block, and let’s see how global scoped var
variable works first.
var hi = 'hi there'; // 1. declare & init global variable hi
console.log(hi); // 2. print 'hi there'
function myFunction() {
console.log(hi); // 4. print 'hi there'
}
myFunction(); // 3. call the function
- The
var
variablehi
declared on the top is global scoped being outsidemyFunction()
and is assigned (initialized) with the value ‘hi there’. console.log(hi);
that is executed outside ofmyFunction()
successfully prints ‘hi there’ (becausehi
is a global scoped variable).- The code
myFunction();
executed outside ofmyFunction()
calls the functionmyFunction()
. - Within the function,
console.log(hi);
prints ‘hi there’ successfully. The reason it’s available is becausehi
is a global scoped variable and it can also be accessed from anywhere inside any block{}
such as function, inner function, if-else statement, etc.
Now, let’s slightly modify the code snippet to see how block scoped var
variable works as seen below.
var hi = 'hi there'; // 1. declare & init global variable hi
console.log(hi); // 2. prints 'hi there'
function myFunction() {
var hello = 'hello there'; // 4. declare & init block scope variable hello
}
console.log(hello); // 3. Uncaught ReferenceError: hello is not defined
hi
still works as a global variable and you can see that console.log(hi);
prints ‘hi there’ as expected.
However, when console.log(hello);
is executed outside of myFunction()
, it outputs an error. The reason is because hello
is a block scoped variable that is declared and is initialized inside myFucntion(){}
.
How can we fix it?
The solution is simply not breaking the scope rule of var
.
var hi = 'hi there'; // 1. declare & init global variable hi
console.log(hi); // 2. prints 'hi there'
function myFunction() {
var hello = 'hello there'; // declare & init block scope variable hello
console.log(hello); // 4. prints 'hello there'
}
myFunction(); // 3. call myFunction()
As you can see above, the code myFunction();
only calls the function myFunction()
(and its job is done).
console.log(hello);
inside myFunction()
then prints ‘hello there’ successfully without any error, because, again, hello
is a block scope variable within the function.
Reusability of var
You can re-declare another var
variable with the same name as the existing one, as well as re-initializing (re-assigning) a new value into either the existing or a new var
variable.
var hi = 'hi there';
var hi = 'hello there';
hi = 'hi there again';
console.log(hi); // prints 'hi there again'
You can even use var
variable without a proper variable declaration in type. The following code declares the variable hi
as var
in type by default, and hi
keeps on being re-initialized with new value with no error.
hi = 'hi there';
hi = 'hello there';
hi = 'hi there again';
console.log(hi); // prints 'hi there again'
Moreover, you can even use any var
variable without initialization (no value in it).
var hi;
console.log(hi); // prints 'undefined'
Do you think it’s a cool stuff or a headache? It should absolutely be cool if you consciously use var
variables, knowing how it works.
On the other hand, what if you have hundreds of thousands lines of codes with complex logic? You will be likely to unrealize when and where var
variables are (re)declared and (re)initialized. Also, it’s hardly easy to find errors and bugs once something goes wrong.
All of these behaviors of var
is deeply related to hoisting.
Hoisting var
Hoisting in JS means moving variable declarations onto the top of their scope before any code execution.
Say, you try to use a var
variable hi
before declaring and initializing it in either one of the following ways.
Both will work with no errors. How and why can they work without any errors?
-
console.log(hi); // undefined var hi = 'hi there'; console.log(hi); // hi there
-
console.log(hi); // undefined hi = 'hi there'; var hi; console.log(hi); // hi there
Because the JS hoisting mechanism restructures both cases as follows.
var hi;
console.log(hi); // undefined
hi = 'hi there';
console.log(hi); // hi there
- Hoist (move) the variable declaration
var hi;
to the top of its scope and initialize it with such an empty valueundefined
, which is quite similar tonull
(not the same!) in some of the programming languages. - Print it.
- Re-initialize
hi
with the value ‘hi there’. - Print it.
In a sense, this hoisting behavior makes it easier to do coding as you can use var
anytime and anywhere in your program.
However, although it’s completely up to you in taking advantages of var
, many programmers and developers have found that it’s difficult to deal with var
due to its loose syntax; easy-to-use origin.
Roughly speaking, it is error prone in use rather than the syntax of var
itself is so.
If you are new to JS, I recommend to not use var
variable for a while, but, instead, go for let
and/or const
variables first.
Later, when you get used to JS variables in overall, use var
consciously in your code as necessary!
What is let
?
let
came out to the JS world overcoming the drawbacks of var
.
Variable declared with let
is a block scoped variable that can only be used within a block enclosed with curly brackets {}
such as function
, and if-else
, for
, while
, etc.
Let’s see an example below.
The let
variable num
is declared and is initialized with a numeric value 1 as a block scope variable in myFunction()
. On the function call, console.log(num)
that is executed in the same function block successfully prints ‘1’.
function myFunction() {
let num = 1; // block scope variable in the function
console.log(num); // prints 1
}
myFunction(); // call the function
If console.log(num)
is executed outside myFunction()
, it outputs an error, because num
is, again, a block scoped variable that is only accessible within the function block it’s declared and initialized.
function myFunction() {
let num = 1; // block scope variable in the function
}
myFunction(); // call the function
console.log(num); // Uncaught ReferenceError: num is not defined
Let’s see another example below.
num
still works as a block scope variable as verified by console.log(num);
printing ‘1’ successfully.
Now, have a close look at the if
statement block and guess why console.log(printMe);
outputs an error.
function myFunction() {
let num = 1; // block scope variable in the function
console.log(num); // prints 1
if (num == 1) { // if block; an inner block in the function
// a block scope variable in the if block
let printMe = 'print me'; // 'print me'
}
console.log(printMe); // ReferenceError: printMe is not defined
}
myFunction(); // call the function
Within myFunction()
, there’s an if
statement having a let
variable printMe
in its block. In other words, the if
block is an inner block within the myFunction()
block.
console.log(printMe);
that is executed inside myFunction()
, but outside the if
block then occurs a reference error, because printMe
is another let
variable at block scope that can only be consumed inside the if
block.
If so, how can we fix it?
To not break the block scope rule of let
, we can simply move the code console.log(printMe);
into the if
block as seen below.
function myFunction() {
let num = 1; // block scope variable in the function
console.log(num); // prints 1
if (num == 1) { // if block; an inner block in the function
// a block scope variable in the if block
let printMe = 'print me'; // 'print me'
console.log(printMe); // prints 'print me'
}
}
myFunction(); // call the function
Lastly, let’s slightly modify the code snippet and check what changes are as follows.
function myFunction() {
let num = 1; // block scope variable in the function
if (num == 1) { // if block; an inner block in the function
// a block scope variable in the if block
let printMe = 'I got number ' + num; // 'I got number 1'
console.log(printMe); // prints 'I got number 1'
num = num + 1; //num = 2;
printMe = 'I got number ' + num; // 'I got number 2'
console.log(printMe); // prints 'I got number 2'
}
}
myFunction(); // call the function
While num
is a block scoped variable declared in myFunction
, it can also be global to any inner block like the if
block that is in myFunction()
. That means, let
variable can be global or local (block scoped) to the block in which it is declared.
As highlighted above, num
is accessible inside the if
block, and printMe
, as a result, is able to update (re-initialize) its value by accessing num
globally within the function.
Reusability of let
You can re-initialize the existing let
variable with a new value.
function myFunction() {
let num = 1; //block scope
num = 2; //num is reinitialized with 2
console.log(num); //prints '2'
}
myFunction(); // call the function
However, you cannot re-declare another let
variable with the same name as the existing one in the same block.
function myFunction() {
let num = 1; // block scope
num = 2;
let num = 1000; // error point
console.log(num); // SyntaxError: Identifier 'num' has already been declared
}
myFunction(); // call the function
Hoisting let
Hoisting a let
variable moves its declaration to the top of its scope, but without initialization.
Look at the example below.
console.log(hi);
only accesses and reads the variable declaration let hi;
that is hoisted to the top of its scope from the code let hi = 'hi there';
on the next line, and it outputs a reference error. Because the JS program interprets that hi
is not initialized due to the hoisting syntax of let
.
Thus, if you attempt to use a let
variable before a proper declaration (and initialization), the JS program simply doesn’t move to the next line, as opposed to var
being initialized with undefined
by default.
function myFunction() {
//Uncaught ReferenceError: Cannot access 'hi' before initialization
console.log(hi);
let hi = 'hi there';
}
myFunction();
In other words, the JS program interprets the code above like below.
function myFunction() {
//Uncaught ReferenceError: Cannot access 'hi' before initialization
console.log(hi);
let hi;
}
myFunction();
However, if you declare a let
variable first and then use it, there’s no error.
As seen below, console.log(hi);
prints undefined
, because hi
is initialized with undefined
when it’s declared.
function myFunction() {
let hi;
console.log(hi); // prints 'undefined'
hi = "I have some value";
console.log(hi); // prints 'I have some value'
}
myFunction();
What is const
?
In JS, a variable declared with const
in type is block scoped, working fairly the same as let
variable in that it can only be consumed within the block it’s declared and initialized.
function myFunction() {
const hi = 'hi there'; // block scope
console.log(hi); // prints 'hi there'
}
myFunction(); // call the function
console.log(hi); // Uncaught ReferenceError: hi is not defined
Also, a const
variable cannot be re-declared with the same variable name as the existing one in the same block.
function myFunction() {
const hi = 'hi there'; //block scope in a function
const hi = 'hi there again'; // SyntaxError: Identifier 'hi' has already been declared
console.log(hi);
}
myFunction(); // call the function
Like let
, const
variable can be global or local (block scoped) to the block in which it is declared and is initialized. Let’s take a look at the following example.
function myFunction() {
const hello = 'hello'; // block scope in the function
// if block; an inner block in the function
if (hello.length === 5) {
// a block scope variable in the if block
// with the value ' consists of 5 letters.'
const there = ' consists of ' + hello.length + ' letters.';
// prints 'The word hello consists of 5 letters.'
console.log('The word ' + hello + there);
}
}
myFunction(); // call the function
The const
variable hello
is declared and is initialized in myFunction()
, and the if
statement block, that is an inner block in myFunction()
, has a const
variable there
.
Within the if
block, part of the value in there
is initialized with ‘5’ by accessing hello.length
, and console.log('The word ' + hello + there);
successfully outputs ‘The word hello consists of 5 letters.’, which is a derive result by accessing hello
globally and there
locally within the function.
To emphasize again, as hello
is declared and initialized in myFunction()
, while it’s a block scope variable (local scope variable) to the myFunction()
block, it’s also a global scope variable to any inner block within the myFunction()
block.
Now, let’s move onto having a look at what differences are between const
and let
.
A const
variable cannot be re-initialized with any new value unlike let
. The keyword const
is short for constant meaning that it’s fixed, immutable, or static in Maths. Thus, a const
variable should be declared and be initialized at the same time as a single statement.
function myFunction() {
const hi = 'hi there'; // valid
hi = 'hi there again'; // TypeError: Assignment to constant variable.
console.log(hi);
}
myFunction(); // call the function
Also, you cannot use any const
variable without initialization.
function myFunction() {
const hi; //Uncaught SyntaxError: Missing initializer in const declaration
console.log(hi);
}
myFunction(); // call the function
const
variable as an object
Ironically, the properties of a const
object can be updated (re-initialized).
As you can see the code snippet below, the const
object transaction
has updated the values of its properties at the end.
function myFunction() {
const transaction = {
item: "apple",
price: 2
};
console.log(transaction); // { item: 'apple', price: 2 }
transaction.item = "banana";
transaction.price = 5;
console.log(transaction); // { item: 'banana', price: 5 }
}
myFunction(); // call the function
Hoisting const
The declaration of a const
variable is hoisted to the top of its scope without initialization.
Let’s have a look at the reference error message below.
console.log(hi);
knows the existence of hi
as it accesses and reads the variable hi
whose declaration const hi;
is hoisted to the top of the function block from the code const hi = 'hi there';
. However, it cannot access the value of hi
since the initialization of hi
is not hoisted.
function myFunction() {
//Uncaught ReferenceError: Cannot access 'hi' before initialization
console.log(hi);
const hi = 'hi there';
}
myFunction();
What about the following code snippet?
When console.log(hi);
is executed, there shows a slight different error in type, apparently looking the same error as above.
The declaration of hi
is still hoisted to the top of its scope, and the error this time is a syntax error “Missing initializer in const declaration” meaning that const
variable must be initialized on its declaration.
Thus, make sure to stick to using const
variables after declaration & initialization!
function myFunction() {
//Uncaught SyntaxError: Missing initializer in const declaration
console.log(hi);
const hi; // hi is declared, but is not initialized
}
myFunction();
It may take a little time to understand JS variables, but you will soon get used to features, behaviours, and syntax of them, as far as you keep on getting your hands dirty writing JS codes!