Scope And Hoisting
Let’s take a closer look at two fundamental concepts Scope and Hoisting.
Block Scope vs Global Scope
Section titled “Block Scope vs Global Scope”Scope determines the accessibility of variables in different parts of the program. JavaScript supports two main types of scope:
- Global Scope: Variables declared outside of any function body belong to the global scope. They can be accessed and modified anywhere in the code.
- Function Scope: Variables declared inside a function are only accessible within that function and any nested functions.
- Block Scope: Introduced with ES6, variables declared using
let
orconst
have block scope. This means they are accessible only within the surrounding curly braces{}
.
{ let blockScoped = "I exist only in this block!"; console.log(blockScoped); // This works}console.log(blockScoped); // ReferenceError: blockScoped is not defined
In contrast, variables declared with var
are not block-scoped, which can often lead to unexpected results.
Variable Hoisting
Section titled “Variable Hoisting”Hoisting is JavaScript’s default behavior of moving variable and function declarations to the top of their respective scopes during the compilation phase.
With var
Section titled “With var”Variables declared with var
are hoisted, but their values are not initialized until the code execution reaches the declaration.
console.log(hoistedVar); // undefinedvar hoistedVar = "I was hoisted!";console.log(hoistedVar); // "I was hoisted!"
With let
and const
Section titled “With let and const”Variables declared with let
and const
are also hoisted, but they are placed in a “temporal dead zone” from the start of the block until the declaration.
console.log(blockScopedVar); // ReferenceError: Cannot access 'blockScopedVar' before initializationlet blockScopedVar = "This won't work with let or const!";
Function Hoisting
Section titled “Function Hoisting”Functions declared using the function
keyword are hoisted, and their definition is available for use before they are defined in the code.
sayHello();function sayHello() { console.log("Hello, Hoisting!");}
However, functions assigned to variables AKA function expressions (using var
, let
, or const
) are not fully hoisted.
They behave like variables being assigned to function expressions.
The this
Keyword in Functions
Section titled “The this Keyword in Functions”this
holds a reference to the execution context, which depends on how the function is called.
In Regular Functions
Section titled “In Regular Functions”The value of this
depends on where the function is called.
function regularFunction() { console.log(this);}regularFunction(); // In non-strict mode: global object (e.g., `window`) // In strict mode: undefined
In Arrow Functions:
Section titled “In Arrow Functions:”Arrow functions do not have their own this
, instead, they inherit it from their surrounding scope.
const obj = { arrowFunction: () => { console.log(this); },};obj.arrowFunction(); // Inherits `this` from the surrounding scope ie, In non-strict mode: global object (e.g., `window`) // In strict mode: undefined
Regular function vs Arrow function behaviour example
const obj1 = { regularFunc: function () { console.log(this); // `this` refers to obj1 }, arrowFunc: () => { console.log(this); // `this` refers to outer scope },};// Above example in actionconst obj2 = { name: "example", regularMethod: function () { console.log(this.name); // "example" }, arrowMethod: () => { console.log(this?.name); // undefined },};
Example to see how it works in nested functions
const obj3 = { name: "test", outer: function () { // Regular function creates its own `this` console.log("Regular function this:", this.name); // "test"
// Arrow function inherits `this` from outer function const inner = () => { console.log("Arrow function this:", this.name); // "test" }; inner(); },};
If you need the object’s context try using the following.
// Use regular functionconst obj4 = { name: "correct", method: function () { console.log(this.name); // "correct" },};
// Use method shorthandconst obj5 = { name: "correct", method() { console.log(this.name); // "correct" },};
// Bind the functionconst obj6 = { name: "correct", method: (() => { console.log(this.name); }).bind(obj6),};