scope and hoisting in javascript

shubham kabsurishubham kabsuri
6 min read

Introduction:

In JavaScript, two fundamental concepts that every developer must understand are scope and hoisting. These concepts govern how variables, functions, and objects are accessed and managed within a program. Without a clear understanding of how scope and hoisting work, developers may encounter unexpected behavior, bugs, and performance issues.

Whether you're building a small script or a large-scale application, having a strong grasp of scope levels and how hoisting affects code execution will significantly improve the clarity and maintainability of your code. In this post, we'll break down both scope and hoisting, explain their behaviors in different contexts, and provide practical examples to help you avoid common pitfalls.

Great! Let's continue with the next sections of the blog post.


What is Scope in JavaScript?

Scope refers to the context in which variables, functions, and objects are accessible in JavaScript. The scope determines where a particular variable or function can be accessed or modified, and this understanding is crucial for writing clean, error-free code.

In JavaScript, there are three primary types of scope: global scope, function scope, and block scope. Let’s explore each in detail.

1. Global Scope

A variable or function declared outside of any function is considered to be in the global scope. These variables are accessible from anywhere in the code, regardless of where they are called. However, relying too heavily on global variables can lead to naming conflicts and harder-to-maintain code.

Example of Global Scope:

javascriptCopy codelet globalVariable = 'I am global';

function displayGlobal() {
    console.log(globalVariable); // Accessible here
}

displayGlobal(); // Outputs: 'I am global'

2. Function Scope

Variables declared inside a function are only accessible within that function’s scope. This means that once the function execution is complete, those variables are no longer accessible. JavaScript traditionally used var to declare variables, which are function-scoped.

Example of Function Scope:

javascriptCopy codefunction testFunctionScope() {
    let localVar = 'I am local';
    console.log(localVar); // Accessible within this function
}

testFunctionScope();
// console.log(localVar); // Error: localVar is not defined

In this case, localVar is only available inside the testFunctionScope() function and will throw an error if referenced outside it.

3. Block Scope

Introduced with the let and const keywords, block scope is more granular than function scope. It restricts access to variables only within the block (i.e., inside a pair of curly braces {}). This is particularly useful in loops and conditionals.

Example of Block Scope:

javascriptCopy codeif (true) {
    let blockVar = 'I am block-scoped';
    console.log(blockVar); // Accessible inside this block
}

// console.log(blockVar); // Error: blockVar is not defined

Block-scoped variables cannot be accessed outside the block they are defined in, making them more predictable than function-scoped variables declared with var.


Understanding Hoisting in JavaScript

Hoisting is a JavaScript behavior where variable and function declarations are moved to the top of their containing scope during the compile phase. This means that you can refer to variables or functions before they are declared in the code. However, it's important to understand how hoisting works for different types of declarations to avoid confusion and bugs.

How Hoisting Works with var

With the var keyword, variable declarations are hoisted to the top of their scope, but only the declaration itself, not the assignment. This means you can reference the variable before its value is assigned, but it will be undefined until the assignment is made.

Example with var:

javascriptCopy codeconsole.log(myVar); // Outputs: undefined
var myVar = 'I am hoisted';

Here, myVar is hoisted to the top of its scope, but only the declaration (var myVar) is hoisted, not the assignment. Thus, myVar is undefined at the time of logging.

Hoisting with let and const

Unlike var, variables declared with let and const are hoisted but not initialized. Accessing them before they are assigned will result in a ReferenceError. This occurs because these variables are placed in a "temporal dead zone" from the start of the block until they are initialized.

Example with let and const:

javascriptCopy codeconsole.log(myLet); // Throws ReferenceError
let myLet = 'I am hoisted with let';

console.log(myConst); // Throws ReferenceError
const myConst = 'I am hoisted with const';

In both cases, attempting to access myLet or myConst before they are initialized results in an error, highlighting the difference between let/const and var.

Hoisting with Functions

Function declarations are also hoisted, meaning you can call a function before it’s defined in the code.

Example with Function Declaration:

javascriptCopy codemyFunction(); // Works fine

function myFunction() {
    console.log('This function is hoisted!');
}

In contrast, function expressions (when a function is assigned to a variable) are not hoisted in the same way. The variable declaration is hoisted, but the function definition is not.

Example with Function Expression:

javascriptCopy codemyFunc(); // Throws TypeError: myFunc is not a function

var myFunc = function() {
    console.log('This function is not hoisted!');
};

Here, the variable myFunc is hoisted, but the function expression itself is not initialized until the assignment is encountered, resulting in an error if you try to call it beforehand.


Practical Examples

To solidify your understanding of scope and hoisting, let’s look at a few practical examples.

Example 1: Scope with var, let, and const

javascriptCopy codefunction exampleScope() {
    var a = 'var variable';
    let b = 'let variable';
    const c = 'const variable';

    if (true) {
        var a = 'Reassigned var variable';
        let b = 'Block-scoped let variable';
        // const c = 'Block-scoped const variable'; // Error: Reassignment of const
        console.log(a); // Reassigned 'a' will be accessible in the entire function scope
        console.log(b); // 'b' will be block-scoped
    }

    console.log(a); // Outputs: 'Reassigned var variable'
    console.log(b); // Outputs: 'let variable'
    console.log(c); // Outputs: 'const variable'
}

exampleScope();

Example 2: Hoisting with var, let, and const

javascriptCopy codeconsole.log(foo); // undefined
var foo = 'var variable';

console.log(bar); // ReferenceError: Cannot access 'bar' before initialization
let bar = 'let variable';

Example 3: Hoisting with Function Declarations and Expressions

javascriptCopy codegreet(); // Works because function declarations are hoisted

function greet() {
    console.log('Hello!');
}

hello(); // TypeError: hello is not a function

var hello = function() {
    console.log('Hi!');
};

Common Pitfalls and Best Practices

Understanding scope and hoisting can help you avoid several common pitfalls in JavaScript. Here are some best practices:

  1. Declare Variables Before Using Them: To avoid unexpected behavior, always declare variables at the top of their scope (whether function or block).

  2. Avoid Excessive Use of Global Variables: Minimize global variables to reduce the risk of naming conflicts and unintended side effects.

  3. Use let and const Instead of var: Prefer let and const for variable declarations as they provide block scope and avoid issues with hoisting.

  4. Function Declarations Over Expressions: If you need to ensure that a function is accessible throughout its scope, use a function declaration, as it’s hoisted to the top.


0
Subscribe to my newsletter

Read articles from shubham kabsuri directly inside your inbox. Subscribe to the newsletter, and don't miss out.

Written by

shubham kabsuri
shubham kabsuri