Exploring the scope and scope chain in JavaScript

In this article, we will dive deep into different types of scopes and how they work in JavaScript. We will also touch on the concepts of lexical scoping and scope chain. Let's start with the concept of scope first.

Scope

A scope is an environment in which variables and functions are created. JavaScript has three types of scopes: global scope, function scope, and block scope.

Global scope

Global scope is created with the top-level variables and functions that are located outside of any function in a script.

const name = 'John';

function logger() {
  console.log(name); // John
}

logger();

In the above code, the variable name and the function logger create a global scope. Since variables and functions created in the global scope are accessible from anywhere within the scope, we can access the name variable inside the logger function.

Function scope

Every function in JavaScript creates its own scope, where variables created within that scope are accessible only inside that scope. Function scope is also referred to as local scope.

function logger() {
  const age = 30;
  console.log(age); // 30
}

console.log(age); // ReferenceError: age is not defined

In the above code, the logger function creates a function scope. As per the rule of function scope, the age variable can be accessed inside the function, but trying to access it outside of the function results in a reference error.

Block scope

Starting from ES6, JavaScript introduced the block scope. A block scope is created by a block of code wrapped around curly braces. For example, an if statement or a for loop creates a block scope. Variables created within the block scope are accessible inside that scope and not outside of it.

const name = 'John';

if (name === 'John') {
  const message = 'Good morning';
  console.log(`${message} ${name}`); // Good morning John
}

console.log(message); // ReferenceError: message is not defined

Following the rule, we can access the message variable inside the if block. But when we try to access it outside of the block, we get a reference error.

Important to note that this rule applies to variables created with let and const, which is why they are referred to as block-scoped variables.

On the other hand, variables created with var are function-scoped and accessible both inside and outside of a block scope.

const name = 'John';

if (name === 'John') {
  var message = 'Good morning';
  console.log(message); // Good morning
}

console.log(message); // Good morning

As you can see in the above code, we can access the message variable both inside and outside of the if block as it's created with var.

In strict mode, function declarations are also block-scoped. This means that function declarations created within a block are only accessible inside that block and not outside of it.

'use strict';

const name = 'John';

if (name === 'John') {
  const message = 'Welcome';

  function welcome() {
    console.log(`${message} ${name}`);
  }
  welcome(); // Welcome John
}

welcome(); // ReferenceError: welcome is not defined

In the above code, when we try to access the welcome function outside of the if block, we get a reference error.

In non-strict mode, the behavior is different. Function declarations created within a block scope can be accessed both inside and outside of that scope. Take a look at the example below.

const name = 'John';

if (name === 'John') {
  const message = 'Welcome';

  function welcome() {
    console.log(`${message} ${name}`);
  }
  welcome(); // Welcome John
}

welcome(); // Welcome John

Good to know that function expressions and arrow functions created with var, let, and const follow the same scope principles as variables created with these keywords.

Lexical scoping

Lexical scoping refers to the rules that we can access variables from a parent scope within a child scope while preventing access to variables from a child scope within a parent scope and from a sibling scope within another sibling scope. Lexical scoping uses the location of a variable declaration to determine where that variable is available.

function logger() {
  const name = 'John';

  if (name === 'John') {
    const message = 'Good morning';
  }

  function welcome() {
    const age = 30;
    console.log(name); // John
    console.log(message); // ReferenceError: message is not defined
  }
  welcome();

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

logger();

In the above code, the welcome function being within the child scope of the logger function can access the name variable defined in the logger function (parent scope).

However, trying to access the age variable, which is defined in the welcome function (child scope), from the logger function (parent scope) results in a reference error.

Similarly, trying to access the message variable defined in the if statement (sibling scope) from the welcome function (sibling scope) also results in a reference error.

This is lexical scoping in action.

Scope chain

Scope chain refers to the concept of accessing variables from one scope within another and the sequence of connections created in the process. A scope chain is created when a child scope accesses a variable from its parent scope.

The process is as follows: a child scope, not finding a variable in its own scope, does a variable lookup within its parent scopes. The lookup continues up to the global scope if the variable isn't found within the parent scopes.

Important to know that variable lookup only works from the inner scope to the outer scope and not the other way around. Following are examples of scope chain creation.

const age = 30;

function welcome() {
  const name = 'John';

  function introduce() {
    const intro = "Hi, I'm";
    console.log(`${intro} ${name}. I'am ${age} years old!`); // Hi, I'm John. I'am 30 years old!
  }
  introduce();
}

welcome();

Take a look at the console.log within the introduce function. First, it searches for the intro variable in its scope and finds it.

It then searches for the name variable in its scope and, not finding, it does a variable lookup in its parent scope where it finds the name variable.

Lastly, it searches for the age variable in its scope, then in its parent scope of the welcome function, and finally in the global scope where it finds the age variable.

Notice that accessing each name and age variable within the introduce function links multiple scopes together. This is scope chain creation in action.

With this, it's a wrap for the scope and scope chain in JavaScript. I hope you've learned a thing or two from this article. Writing some code and seeing these in action will reinforce your learning further.

Scope is one of three topics of a JavaScript execution context. If you are interested in learning the other two topics, I recommend checking out the articles on Understanding the dynamic nature of the ‘this’ keyword in JavaScript, as well as Understanding the variable environment and hoisting in JavaScript for further insights.

Share this article with your friends

Copy URL

Elevate your JavaScript and freelance journey

Supercharge your JavaScript skills and freelance career. Subscribe now for expert tips and insights!