Hello folks. Welcome to my another post.
In this post, we will learn about var
, let
and const
from the basics to the magical parts of it. So please bear with me till the end.
Variables
- Variables in programming languages are means to store information in memory and assign a human-readable label to it for future references.
- There are a few rules for variable naming:
- The name must contain only letters, digits, or the symbols $ and _
- The first character must not be a digit
- It can't contain any whitespace characters
- There are some reserved words that can't be used as variable names
- In Javascript, there are two types of variables: Primitives and Reference types. Boolean, string and number are examples of primitive types while objects and arrays are examples of reference type.
- Javascript is a dynamically typed language. That means we can assign different types to different variables without having an error(for
var
andlet
keywords and notconst
) - In Javascript, we can declare a variable using
var
,let
andconst
.
Some of the terms that I will be using in this post
Scope
- Scope in Javascript refers to the variable's accessibility in the code. Based on the scope of a variable, some variables can be accessed in some part of the code while some can't be accessed in that part of the code.
- There are three types of scopes: Global, Function and Block.
- Variables declared at the top level(outside any function) are global scoped. They can be accessed throughout the program.
- Variables declared inside a function are function scoped and can only be accessed inside that function. It will throw a reference error if tried to access outside the function.
- Variables declared inside
{}
are called block scoped and their accessibility depends on the keyword that was used to declare them(fromvar
,let
andconst
).
Scope chain
- Javascript creates scopes for every executing function and
{}
block. There is also a global scope that holds some special values and variables that are in the global scope. - Each scope has access to the parent scope in which it is defined. By using it, the current scope can access the variables from the parent scope. This creates a chain of scope which is called a scope chain.
Hoisting
- JavaScript Hoisting refers to the process whereby the interpreter appears to move the declaration of functions, variables or classes to the top of their scope, before execution of the code.
I won't be going deep into any of these topics right now. (Maybe in the future posts 😉)
Now let's learn about var
, let
and const
.
var
- The
var
keyword is an old way of creating variables in Javascript. - Javascript engine doesn't throw an error if we try to create two variables of the same name in the same scope using
var
. If the second statement is an assignment then it will replace the value within the variable. If the second statement is just a declaration then it will be ignored. Javascript engine won't throw an error here.
var test = "Hello";
var test;
console.log(test); // Hello
var test = "Nice";
console.log(test); // Nice
- The scope of a variable declared with
var
is its current execution context and closures(Maybe in the future post). In simpler words,var
declarations are function scoped and accessible inside that function and variables that are declared in the global scope are accessible anywhere.
function testFn() {
var test1 = "Hello";
if (true) {
var test2 = "Nice";
}
console.log(test1, test2); // Hello Nice
function innerFn() {
var test3 = "Wow";
console.log(test1, test2); // Hello Nice
}
// test3 is not accessible here.
// It will throw a ReferenceError.
}
testFn();
// test1, test2 and test3 are not accessible here.
// They will throw a ReferenceError.
- When using
var
, we can modify or re-assign any type of primitive values or reference values.
var test = "Hello";
test = "Nice";
console.log(test); // Nice
test = 5;
console.log(test); // 5
test = ["wow"];
console.log(test); // ["wow"]
-
var
declarations are hoisted and initialized with the valueundefined
. What this means is that we can use a variable before it is declared but it won't have any value till any one of the assignment statement gets executed.
console.log(test); // undefined
// It didn't throw an error 🙂
var test = "Hello";
let
-
let
keyword is used for creating block scoped variables. - Unlike
var
, we can't have two variable declarations usinglet
with the same name inside the same scope. It will throw an error.
let test = "Hello";
let test = "Bad";
// SyntaxError: Identifier 'test' has already been declared
- The scope of a variable declared with
let
is the curly brackets containing the variable and for the global scope, it is accessible after the declaration throughout the program.
if (true) {
let test = "Hello";
console.log(test); // Hello
}
console.log(test); // ReferenceError: test is not defined
-
let
declarations are also hoisted but not initialized. That means accessing a variable before its declaration will throw an error.
console.log(test); // ReferenceError: test is not defined
let test = "Hello";
- Same as
var
, when usinglet
, we can modify or re-assign any type of primitive values or reference values.
const
-
let
andconst
are the same. The only difference is in the modification and re-assignment of the variable. - All the variables declared using
const
and having a primitive value can't be modified or re-assigned. It will throw an error if tried to do so.
const test = "Hello";
test = "Wow"; // TypeError: Assignment to constant variable.
- All the variables declared using
const
and having a reference type value, can be modified but can't be re-assigned.
const test = ["Hello"];
test.push("World");
console.log(test); // ["Hello", "World"]
test = ["Wow"]; // TypeError: Assignment to constant variable.
Now let's demystify some of the magical cases.
Case 1
- Let's try to assign a value to a variable before its declaration with
let
(orconst
) and see what happens.
test = "Bad";
// ReferenceError: Cannot access 'test' before initialization
let test = "Hello";
- As expected, this gives an error. But a lot is going on here and let's try to understand it.
- Here
test
is declared usinglet
, so it will get hoisted, but it won't get initialized. Since it doesn't get initialized, trying to assign it a value will give an error "Cannot access 'test' before initialization". - Now let's try to do the same thing with
var
and see what happens.
console.log(test); // undefined
test = "Wow";
console.log(test); // Wow
let test = "Hello";
console.log(test); // Hello
- Here
var
declaration is first hoisted and then initialized with theundefined
value which is why the first console will printundefined
. - Then as the variable is initialized assigning a value
Wow
to it works fine and the second console printsWow
. - When the Javascript engine comes to the
let
declaration it simply assigns the valueHello
to it and that is why the third console printsHello
.
Case 2
- Let's see an interesting case with hoisting and variable shadowing.
let test = "Hello";
if (true) {
let test = "Wow"; // Will this throw an error???
console.log(test); // Will this execute???
}
console.log(test);
- Let's try to dissect it.
- Here we have declared a variable named
test
and initialized it with the valueHello
. - Then when it enters the
if
block, it will create a new scope. As always, Javascript will hoist the declaration of thetest
variable and it won't get initialized as it is declared usinglet
. - Then the Javascript engine will assign it the value
Wow
. It will work as thelet
is block scoped and Javascript can have the same named variables in different scopes. - Now when we reach the console Javascript engine will try to find the variable in the current scope and as the current scope has the variable with the name
test
it will use it and it won't use the variable from the parent scope. This is called variable shadowing. - As the inner variable's scope is over with
if
's curly brackets, the last console will printHello
. - Let's look at an example with a small variation.
let test = "Hello";
if (true) {
console.log(test); // 🤔
let test = "Wow";
console.log(test);
}
console.log(test);
- Here when the Javascript engine enters the
if
block, it will create a new scope. As always Javascript engine will hoist the declaration of thetest
variable and it won't be initialized as it is declared usinglet
. - So as we can guess now there is a variable with an uninitialized state in the current scope so Javascript won't use the parent value and throw
ReferenceError: Cannot access 'test' before initialization
. - Now let's look at the same example using
var
var test = "Hello";
if (true) {
console.log(test); // 🤔
var test = "Wow";
console.log(test);
}
console.log(test);
- Here when the Javascript engine enters the
if
block, it will create a new scope. As always Javascript will try to hoist the declaration of thetest
variable but the variables declared usingvar
are not block scoped, they are function scoped. - Javascript engine will not hoist it as a same named variable is already there in the current scope. So the first console will use the value from the parent which is
Hello
. - When the engine reaches the declaration of the
test
variable inside theif
block it is treated as the declaration of the same named variable as thevar
is function scoped and the engine will simply assign the valueWow
to thetest
variable and the second console will printWow
. - As the parent variable is re-assigned with the new value, the third console will also print
Wow
.
Bear with me there is more 😁
Case 3
- Let's look at an interesting case of
var
inside theif
block.
if (false) {
var test = "Hello";
}
console.log(test); // Reference error??? 🤔
- Here as we can see that the if block doesn't get executed as the condition, is false, so it should throw a Reference error. Right? Right???
- Well here it won't throw a Reference error and instead, it prints
undefined
🙂. - The reason for this is that the Javascript engine still hoists the
test
variable even if this code doesn't get executed and our global scope is now polluted with an extra unnecessary variable. One of the reasons why you should avoid usingvar
😅. - In the older code you may see an interesting pattern called IIFE - Immediately Invoked Function Expression through which people avoided the scope pollution.
if (false) { // or true
(function () {
var test = "Hello";
// Some code that uses test
})(); // Note the invocation here
}
console.log(test); // ReferenceError: test is not defined
- Here we have created an anonymous function and immediately called it. Javascript treats it as an expression(thus IIFE).
- As we know that the
var
is function scoped and thus it can't be accessed outside the anonymous function.
Case 4
- Let's look at some of the weird cases of the variables declared using
var
in the case offor
loops. Let's start with a simple example.
for (var i = 0; i < 3; i++) {
// Do something
}
console.log(i); // 3
- As we can see here that the console prints the value
3
and that is because the variables declared usingvar
are function or global scoped and not block scoped. So herei
is accessible even after thefor
loop. Again scope pollution 🙂. - Let's look at another famous
for
loop problem withvar
var fnArray = [];
for (var i = 0; i < 3; i++) {
fnArray[i] = function () {
console.log(i);
};
}
for (var j = 0; j < 3; j++) {
fnArray[j]();
} // 0, 1 and 2 ??? 🙂
- Here we may think that it should print
0
,1
and2
but it won't and let me tell you why. - Here we have created an array named fnArray and we have pushed some functions in it which is using the variable
i
from thefor
loop. - We know that
var
is function scoped so its accessibility doesn't have to do anything with thefor
loop. The function is using the variablei
but it will only access its value when it is being executed. - In the last iteration of the first
for
loop,i++
will be executed with the value2
and it will become3
which will stop the loop. Now variablei
will be accessible outside thefor
loop with value3
. - Now when the second
for
loop gets executed, it will call the anonymous function which will try to console the value of the variablei
and as the value ofi
is now3
it will print3
three times. - This problem can be solved easily by using
let
in the firstfor
loop.
var fnArray = [];
for (let i = 0; i < 3; i++) {
fnArray[i] = function () {
console.log(i);
};
}
for (var j = 0; j < 3; j++) {
fnArray[j]();
} // 0, 1 and 2 as expected
- This will work because the
let
variables are block scoped. So each iteration of thefor
loop will create a scope and it will hold the value ofi
for that iteration. - So when the function will try to access the value of
i
, it will see the correct value in the scope created by thefor
loop and print0
,1
and2
as expected.
Summary
So that's it for today folks 😅.
Thanks for bearing with me till the end. Give the post a Heart if you liked the post and give a comment or ping me in case I have missed anything.
You can reach me on:
- Github
- Email : yash.kalaria93@gmail.com
Top comments (1)
Really great article. appreciate your efforts.