[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
varvariablehideclared 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’ (becausehiis 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 becausehiis 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
hiwith 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!