Skip to main content

Scopes and Hoisting

Hoisting

Hoisting is a JavaScript mechanism where variables and function declarations are moved to the top of their scope before code execution. This means that if a variable is declared anywhere in a function, it is moved to the top of the function for execution regardless of whether the declaration is at the top of the function or not.

function myFunction() {
console.log(myVar); // undefined
var myVar = "Hello";
console.log(myVar); // "Hello"
}

myFunction();

In this example, the myVar variable is declared after the first console.log() statement, but it is still accessible within the function. This is because the declaration is hoisted to the top of the function before execution.

Need for hoisting

Hoisting exists in JavaScript because it is a compiled language. This means that before JavaScript code is executed, it is first compiled into a format that is more suitable for execution. During this compilation process, variable and function declarations are moved to the top of their scope. This allows variables and functions to be used before they are declared.

Hoisting let VS var

Consider this following code

console.log(a);
console.log(b);

let a = 10;
var b = 20;

For a we would get ReferenceError: Cannot access 'a' before initialization and for b we would get a special placeholder of undefined. This is because of hoisting.

Hoisting let VS const

So both of them works the same way in terms of hoisting the only difference is that const is way more strict than let.

let a;
a = 10; // we can initialize it later

const b; // this will give an error

This is because const is meant to be initialized at the time of declaration. We can't do it later on.

Let and Var

var

  • Function scoped when it is declared within a function. Available only within that function.
  • Global scoped when it is declared outside a function. Available globally.
function myFunction() {
if (true) {
var x = 10;
}

console.log(x); // Output: 10
}

myFunction();

In this example, the variable x is declared using var inside the if statement, but it is still accessible when we try to log its value outside the if statement. This is because var does not have true block scope; it creates function-scoped variables.

let

  • Block scoped. A block is a set of statements wrapped in curly braces. Each block has its own lexical scope.
  • Not hoisted to the top of its block. If a variable is declared using let, it is only available from the point at which it is declared until the end of the block.
function myFunction() {
if (true) {
let y = 20;
console.log(y); // Output: 20, as 'y' is declared inside the block
}

console.log(y); // Error: 'y' is not defined, as outside the block
}

myFunction();

Can I redeclare let and const variables

No, you cannot redeclare let and const variables. If you do, it throws below error

Uncaught SyntaxError: Identifier 'someVariable' has already been declared

Explanation: The variable declaration with var keyword refers to a function scope and the variable is treated as if it were declared at the top of the enclosing scope due to hoisting feature. So all the multiple declarations contributing to the same hoisted variable without any error. Let's take an example of re-declaring variables in the same scope for both var and let/const variables.

var name = "John";
function myFunc() {
var name = "Nick";
var name = "Abraham"; // Re-assigned in the same function block
alert(name); // Abraham
}
myFunc();
alert(name); // John

The block-scoped multi-declaration throws syntax error,

let name = "John";
function myFunc() {
let name = "Nick";
let name = "Abraham"; // Uncaught SyntaxError: Identifier 'name' has been declared
alert(name);
}

myFunc();
alert(name);

Is const immutable ?

No, the const variable doesn't make the value immutable. But it disallows subsequent assignments(i.e, You can declare with assignment but can't assign another value later)

const userList = [];
userList.push("John"); // Can mutate even though it can't re-assign
console.log(userList); // ['John']

Temporal dead zone

The time between the variable being declared and being initialized is called the temporal dead zone.

console.log(x);
console.log(a);
let a = 10;

In the above code, the temporal dead zone is between the let a = 10 and console.log(a). a will be assigned a value of undefined during the temporal dead zone.

For x we get ReferenceError: x is not defined and for a we get ReferenceError: Cannot access 'a' before initialization. This is because x is not declared at all and a is declared but not initialized.

Scopes

Block scope

Block scope is the area within if, switch conditions or for and while loops. Generally speaking, whenever you see {curly brackets}, it is a block. In ES6, const and let keywords allow developers to declare variables in the block scope, which means those variables exist only within the corresponding block.

Lexical scope

Lexical scope means that in a nested group of functions, the inner functions have access to the variables and other resources of their parent scope. This means that the child functions are lexically bound to the execution context of their parents. Lexical scope is sometimes also referred to as Static Scope.

Global scope

Global scope refers to the context within which variables are accessible to every part of the program. If a variable is declared outside of all functions or curly braces (i.e. in the root of the program), it is said to be defined in the global scope.

Variables without Block Scope

(Using var or Function Declarations in Non-Strict Mode)

In JavaScript, when you declare a variable using var or create a function without using strict mode, the variable you declare inside a block of code (between curly braces ) is not limited to that block. It can be accessed from anywhere within the entire function or script that encloses the block.

function exampleFunction() {
if (true) {
var x = 10; // This variable is accessible throughout the function
}

console.log(x); // Output: 10, even though 'x' was declared inside the block
}

exampleFunction();

x is declared inside the block (the if statement), but it is still accessible when we try to log its value outside the block. This is because var does not have true block scope; it creates function-scoped variables.

Variables with Block Scope

(Using let and const):

function exampleFunction() {
if (true) {
let x = 10; // block scoped, only acessible here.
}

console.log(x); // Output: ReferenceError: x is not defined
}

exampleFunction();

In this example, the variable x is declared using let inside the block, and it is not accessible outside the block. This demonstrates the true block scope behavior of let and const.

Shadowing

var a = 100;
{
var a = 200;
console.log(a);
}

console.log(a);

Here the output would be 200 and 200. This is because var a = 100; is in the global scope and var a = 200; even if it is in the block, we learnt that var is function/global scoped. So the var a = 200; is actually shadowing the var a = 100; in the global scope ie they are acting as the same variable.

let a = 100;
{
let a = 200;
console.log(a);
}

console.log(a);

Here the output would be 200 and 100. This is because let a = 100; is in the global scope and let a = 200; is in the block scope. So they are two different variables. SAME FOR CONST.

Illegal shadowing

let a = 100;
{
var a = 200;
console.log(a);
}

This would give an error because we are trying to shadow a let variable with a var variable. This is not allowed.