Scope and the Scope Chain in JavaScript

Scoping in JavaScript refers to how variables are organized in our program and how the JS engine accesses them. Scope is part of the execution context (a concept for another day) and lets the engine know what variables are available to it currently for the execution of code.

Scope is the space or environment in which a particular variable is declared and can be accessed. Let's look at the three types of scope in JavaScript.

Global scope

Any variable declared outside of a function or a block of code is in the global scope. Variables declared in the global scope are accessible throughout the program.

In this example, the variable x is in the global scope, so it is available inside the sum function.

//global scope
const x = 1;

function sum(num1, num2) {
  //x is available inside this function
    num1 = x + 1;

    return num1 + num2
}

Function scope

Any variable declared inside of a function is, you got that right, in function scope. These variables are only available within a function and no code outside of this function has access to such variables.

In the example below, the variable currentYear is function scoped. Trying to access it outside the function will return an error that says such a variable has not been defined.

function calculateAge(birthYear) {
    let currentYear = 2022

    return currentYear - birthYear
}

console.log(currentYear) //'currentYear' is not defined

Block scope

Any variables declared with let or const inside a block of code, such as a for loop or an if statement, are only available inside the block. This also applies to any function inside of a block, but only in strict mode.

"use strict"
let birthYear = 1992

if(birthYear >= 1981 && birthYear <= 1996){
    let priority = 'mental health'

  function age(birthYear){
    return 2022 - birthYear
  }
} 

console.log(priority) //'priority' is not defined
console.log(age(1992)) //'age' is not defined

Lexical scope

I hope it is clear from the types and examples that scoping is controlled by the placement of functions and blocks in the code. So if a variable inside a block scope or function scope cannot be accessed outside of them, this means that sibling scopes cannot access each other. This is known as lexical scoping. In the example below, the second function is logging a variable that is declared in a sibling function.

function calculateAge(birthYear) {
    let currentYear = 2022

    return currentYear - birthYear
}

function getCurrentYearFromAnotherFunction(){
    console.log(currentYear) //'currentYear' is not defined
}

Scope Chain

It is, however, possible for scopes to access each other through something called a scope chain. This is only possible when the relationship is a parent-child one. A child scope can access the parent scope, but the opposite is not true. Let’s look at an example:

let birthYear = 1992

if(birthYear >= 1981 && birthYear <= 1996){
  const currentYear = 2022
  console.log(priority)

  function age(birthYear){
    const priority = 'mental health'
    return currentYear - birthYear
  }
}

The function age is inside the if block, hence it is in the child scope and can access the currentYear variable from the parent block. However, the parent block is unable to access the priority variable declared inside the function.