Table of contents
1.
Introduction
2.
Scope
3.
Global Scope
4.
Local or Function Scope
5.
Lexical Scope
6.
Example
7.
Block Scope
8.
Scope Chain
9.
Example
10.
Frequently Asked Questions
10.1.
What happens if a variable is not found in any scope during the scope chain lookup?
10.2.
Can a variable declared in an inner scope have the same name as a variable in an outer scope?
10.3.
How does the scope chain affect the performance of JavaScript code?
11.
Conclusion
Last Updated: Oct 25, 2024
Easy

Scope Chain in Javascript

Author Rahul Singh
0 upvote
Career growth poll
Do you think IIT Guwahati certified course can help you in your career?

Introduction

In JavaScript, the scope chain is a crucial concept that governs the accessibility and visibility of variables and functions within a program. It determines how different parts of the code can access and use these variables and functions. The scope chain is a set of rules that JavaScript follows to find and retrieve the value of a variable or function when it is requested in the code.

It helps developers avoid common pitfalls like variable name clashes, unexpected behavior, and performance issues. 

Scope Chain in Javascript

In this article, we will discuss the concept of a scope chain in JavaScript. We will learn different types of scopes, like global scope, local scope, lexical scope, and block scope, with proper examples to understand how the scope chain works in real-world problems.

Scope

In JavaScript, scope refers to the visibility and accessibility of variables and functions within a program. It determines where these variables and functions can be accessed and used in the code. JavaScript has different types of scopes, including global scope, local scope, lexical scope, and block scope.

Think of scope as a container or boundary that wraps around variables and functions. The variables and functions defined within a particular scope are only accessible within that scope and any nested scopes. This scoping mechanism helps prevent naming conflicts, provides encapsulation, and allows for better code organization.

JavaScript's scoping rules are based on the scope chain concept. When a variable or function is requested in the code, JavaScript starts looking for it in the current scope. If it doesn't find it there, it moves up the scope chain to the next outer scope and continues the search until it reaches the global scope. This process is called variable or function resolution.

Knowledge of scope is crucial for writing efficient and maintainable JavaScript code. It allows developers to control the visibility and lifetime of variables and functions, which prevents unintended modifications and creates modular and reusable code.

Global Scope

In JavaScript, the global scope is the outermost scope that encompasses the entire program. Variables and functions declared in the global scope are accessible from anywhere within the code, including inside functions and other nested scopes.

To declare a variable in the global scope, you can use the `var` keyword outside of any function or block. For example:

var globalVariable = 'I am global';
function someFunction() {
  console.log(globalVariable); 
}
someFunction();
console.log(globalVariable);
You can also try this code with Online Javascript Compiler
Run Code

 

Output:

 'I am global'


In this example, `globalVariable` is declared in the global scope using the `var` keyword. It can be accessed both inside the `someFunction()` & outside of it.


Note: It's generally recommended to avoid declaring too many variables in the global scope. Overusing global variables can lead to naming conflicts, unintended modifications, and harder-to-maintain code. Instead, it's better to use local scopes and modular patterns to keep the global scope clean.

The global scope also includes built-in JavaScript objects and functions like `window`, `document`, `console`, etc. These are automatically available in the global scope and can be accessed from anywhere in the code.

Now, let’s consider an another example:

// Global scope
var name = 'Rahul';
var age = 30;

function printDetails() {
  // Local scope
  var job = 'Developer';
  console.log(name);
  console.log(age); 
  console.log(job); 
}

printDetails();

console.log(name); 
console.log(age); 
console.log(job); 
You can also try this code with Online Javascript Compiler
Run Code


Output: 

'Rahul'
30
‘Developer’
'Rahul'
30
ReferenceError: job is not defined


In this example, variables `name` and `age` are declared in the global scope using the `var` keyword. These variables are accessible both inside and outside the `printDetails()` function.

Inside the `printDetails()` function, we have a local variable `job` declared with the `var` keyword. This variable is only accessible within the function's local scope.

When we call the `printDetails()` function, it can access the global variables `name` and `age` and the local variable `job`. The function prints the values of these variables to the console.

Outside the function, we can still access the global variables `name` & `age`. However, when we try to access the `job` variable, we get a `ReferenceError` because `job` is not defined in the global scope. It is only accessible within the local scope of the `printDetails()` function.

Local or Function Scope

Local scope, also known as function scope, refers to the visibility & accessibility of variables & functions within a specific function. When you declare a variable or define a function inside another function, it is said to be in the local scope of that function.
Variables declared with the `var` keyword inside a function are local to that function and cannot be accessed from outside the function. They have a lifetime that starts when the function is called and ends when the function finishes execution.

For example : 

function greet() {
  var message = 'Hello';
  console.log(message); 
}

greet();
console.log(message);
You can also try this code with Online Javascript Compiler
Run Code


Output:

 'Hello'
ReferenceError: message is not defined


In this example, the `message` variable is declared inside the `greet()` function using the `var` keyword. It is local to the function & can only be accessed within the function's body.

When we call the `greet()` function, it logs the value of `message` to the console, which is 'Hello'. However, when we try to access `message` outside the function, we get a `ReferenceError` because `message` is not defined in the global scope.

Local scope helps create encapsulation and prevent naming conflicts. It allows variables and functions to be defined and used within a specific context without interfering with other parts of the code.

It's important to note that variables declared with the `let` & `const` keywords also have a local scope, but they are block-scoped rather than function-scoped. We will discuss block scope in a later section.

Let’s consider an another example :

var globalVar = 'I am global';
function outer() {
 var outerVar = 'I am in the outer function';  
 function inner() {
   var innerVar = 'I am in the inner function';
   console.log(globalVar); 
   console.log(outerVar); 
   console.log(innerVar);
 }
 inner();
 console.log(globalVar); 
 console.log(outerVar); 
 console.log(innerVar);
}
outer();
console.log(globalVar); 
console.log(outerVar); 
console.log(innerVar); 
You can also try this code with Online Javascript Compiler
Run Code


Output

I am global
I am in the outer function
I am in the inner function
I am global
I am in the outer function
ReferenceError: innerVar is not defined
I am global
ReferenceError: outerVar is not defined
ReferenceError: innerVar is not defined


In this example, we have a global variable, `globalVar,` and two functions: `outer()` and `inner()`. The `inner()` function is defined inside the `outer()` function, creating a nested scope.

Inside the `inner()` function, we have access to the `globalVar`, `outerVar`, and `innerVar` variables. The `inner()` function can access variables from its own scope, the `outer()` function's scope, and the global scope.


When we call the `outer()` function, it in turn calls the `inner()` function. The `inner()` function logs the values of `globalVar`, `outerVar` & `innerVar` to the console. However, when we try to access `innerVar` outside the `inner()` function, either in the `outer()` function or in the global scope, we get a `ReferenceError` because `innerVar` is limited to the local scope of the `inner()` function.


Similarly, when we try to access `outerVar` outside the `outer()` function, we get a `ReferenceError` because `outerVar` is limited to the local scope of the `outer()` function.

Lexical Scope

Lexical scope, also known as static scope, refers to how variable scope is determined based on the code's structure. In JavaScript, the lexical scope is defined by the placement of variables and functions within the code.

When a function is defined inside another function, the inner function has access to the variables and parameters of the outer function because it is lexically bound to the scope of the outer function.

For example : 

function outer() {
 var outerVar = 'I am in the outer function';
 
 function inner() {
   console.log(outerVar);
 }
 return inner;
}
var closure = outer();
closure();
You can also try this code with Online Javascript Compiler
Run Code


Output

I am in the outer function


In this example, we have an `outer()` function that defines a variable `outerVar` and an `inner()` function. The `inner()` function is defined inside the `outer()` function and has access to the `outerVar` variable due to lexical scoping.

The `outer()` function returns the `inner()` function, which is assigned to the `closure` variable. Even though the `outer()` function has finished executing, the `closure` variable still maintains a reference to the `inner()` function and its lexical scope.

When we invoke the `closure()` function, it still has access to the `outerVar` variable from the lexical scope of the `outer()` function. This is because the `inner()` function retains access to the variables in its lexical scope, even after the `outer()` function has been completed.

Lexical scoping allows for the creation of closures, which are functions that have access to variables from their outer (enclosing) scope even after the outer function has finished executing. Closures are a powerful feature in JavaScript and are widely used for data privacy, function factories, and maintaining state.

Example

Let's look at an another example to understand lexical scope and closures with different scenario:

function counter() {
 var count = 0;
 
 function increment() {
   count++;
   console.log(count);
 }
 
 return increment;
}
var counter1 = counter();
var counter2 = counter();
counter1();
counter1();
counter1();
counter2();
counter2();

You can also try this code with Online Javascript Compiler
Run Code

 

Output

1
2
3
1
2


In this example, we have a `counter()` function that defines a variable `count` and an inner function `increment().` Due to lexical scoping, the increment()` function has access to the `count` variable.


The `counter()` function returns the `increment()` function, which is assigned to the `counter1` and `counter2` variables. Each time `counter()` is called, a new lexical scope is created with its own `count` variable.


When we invoke `counter1()`, it increments the `count` variable within its lexical scope and logs the updated value to the console. Each subsequent call to `counter1()` continues to increment and log the value of `count` specific to its lexical scope.

Similarly, when we invoke `counter2()`, it operates on its own lexical scope independent of `counter1()`. It starts with a new `count` variable initialized to 0 and increments it with each call.

Block Scope

Block scope refers to the visibility and accessibility of variables declared within a block of code, which is typically enclosed by curly braces `{}`. Block scope was introduced in JavaScript with the `let` and `const` keywords in ECMAScript 2015 (ES6).

Before ES6, JavaScript only had function scope and global scope. Variables declared with the `var` keyword were either function-scoped or globally-scoped, regardless of their defined block. This behavior could sometimes lead to unintended consequences and make it harder to manage variable scope.

With the introduction of `let` and `const`, JavaScript gained block scoping. Variables declared with `let` and `const` are block-scoped, meaning they are only accessible within the nearest enclosing block.

For example : 

function example() {
 var x = 1;
 
 if (true) {
   let y = 2;
   const z = 3;
   console.log(x);
   console.log(y);
   console.log(z);
 }
 
 console.log(x);
 console.log(y);
 console.log(z);
}
example();
You can also try this code with Online Javascript Compiler
Run Code


Output

1
2
3
1
ReferenceError: y is not defined
ReferenceError: z is not defined


This example has a function `example()` that demonstrates block scope. Inside the function, we have a variable `x` declared with `var`, which is function-scoped.


Within the `if` block, we declare variables `y` and `z` using `let` and `const`, respectively. These variables are block-scoped and are only accessible within the `if` block.


Inside the `if` block, we can access `x`, `y`, and `z` without any issues. However, outside the `if` block but still within the function, we can only access `x`. Attempting to access `y` or `z` outside the block will result in a `ReferenceError` because they are not defined in that scope.

Block scope allows you to create localized variables within a specific block of code. It helps avoid variable hoisting issues and provides better control over variable accessibility. Block scope also promotes code clarity and reduces the risk of accidentally overwriting variables in outer scopes.

It's important to note that `let` and `const` are not hoisted, unlike `var`. They are only initialized and accessible from the point of declaration within their block.

Let’s look at an another example:

function example() {
 var x = 1;
 
 if (true) {
   let y = 2;
   const z = 3;
   
   console.log(x);
   console.log(y);
   console.log(z);
   
   if (true) {
     let y = 4;
     const z = 5;
     
     console.log(x);
     console.log(y);
     console.log(z);
   }
   
   console.log(x);
   console.log(y);
   console.log(z);
 }
 
 console.log(x);
 console.log(y);
 console.log(z);
}
example();
You can also try this code with Online Javascript Compiler
Run Code


Output

1
2
3
1
4
5
1
2
3
1
ReferenceError: y is not defined
ReferenceError: z is not defined

 

This example has a function `example()` that demonstrates block scope with nested blocks.

  • Inside the function, we have a variable `x` declared with `var`, which is function-scoped and accessible throughout the function.
     
  • Within the first `if` block, we declare variables `y` and `z` using `let` and `const`, respectively. These variables are block-scoped and are only accessible within the first `if` block.
     
  • Inside the first `if` block, we can access `x`, `y`, and `z` without any issues. The values of `y` and `z` are 2 and 3, respectively.
     
  • Within the nested `if` block, we declare new variables `y` and `z` using `let` and `const`, respectively. These variables are block-scoped to the nested `if` block and shadow the variables with the same names from the outer `if` block.
     
  • Inside the nested `if` block, `x` retains its value of 1, while `y` and `z` have the values of 4 and 5, respectively, specific to that block.
     
  • After the nested `if` block, we are back in the scope of the first `if` block. Here, `x` remains unchanged, and `y` and `z` retain their original values of 2 and 3 from the first `if` block.
     
  • Outside the first `if` block but still within the function, we can only access `x`. Attempting to access `y` or `z` will result in a `ReferenceError` because they are not defined in that scope.

Scope Chain

The scope chain is a hierarchical structure that determines the order in which JavaScript looks for variables and functions. It is a set of rules that JavaScript follows to resolve identifier references during code execution.

When a variable or function is referenced in JavaScript code, the JavaScript engine starts looking for its declaration in the current scope. If it doesn't find the declaration in the current scope, it moves up the scope chain to the next outer scope and continues the search. This process continues until the variable or function is found or until the global scope is reached.

The scope chain is created based on the lexical structure of the code. It follows the nested structure of functions and blocks. The outermost scope is the global scope, and each nested function or block creates a new scope within its enclosing scope.

For example : 

var globalVar = 'Global';


function outerFunction() {
  var outerVar = 'Outer';
  
  function innerFunction() {
    var innerVar = 'Inner';
    
    console.log(innerVar); 
    console.log(outerVar); 
    console.log(globalVar); 
  }
  
  innerFunction();
}
outerFunction();
You can also try this code with Online Javascript Compiler
Run Code


Output

‘Inner’
‘Outer’
‘Global’


In this example, we have a global variable `globalVar`, an `outerFunction`, and an `innerFunction` nested inside `outerFunction`.


When `innerFunction` is executed, JavaScript looks for the variables `innerVar`, `outerVar`, and `globalVar` in the following order:

1. It starts in the current scope of `innerFunction` and finds `innerVar`, using its value.
 

2. It looks for `outerVar` in the current scope of `innerFunction` but doesn't find it. It then moves up the scope chain to the outer scope of `outerFunction` and finds `outerVar` there.
 

3. It looks for `globalVar` in the current scope of `innerFunction` and `outerFunction` but doesn't find it. It then moves up the scope chain to the global scope and finds `globalVar` there.
 

The scope chain determines the accessibility and visibility of variables and functions based on their lexical placement in the code. It allows for proper encapsulation and prevents naming conflicts between different scopes.
 

It's important to note that the scope chain is determined at the time of function creation (lexical scoping) and remains fixed throughout the function's lifetime. It does not change based on where the function is invoked from.

Example

Let's consider an example to understand how the scope chain works in an another scenario:

var globalVar = 'Global';


function outerFunction() {
  var outerVar = 'Outer';
  
  function innerFunction() {
    var innerVar = 'Inner';
    
    function nestedFunction() {
      var nestedVar = 'Nested';
      
      console.log(nestedVar);
      console.log(innerVar); 
      console.log(outerVar); 
      console.log(globalVar);
    }
    
    nestedFunction();
  }
  
  innerFunction();
}

outerFunction();
You can also try this code with Online Javascript Compiler
Run Code

 

Output: 

'Nested'
 'Inner'
'Outer'
'Global'


In this example, we have a global variable `globalVar`, an `outerFunction`, an `innerFunction` nested inside `outerFunction`, & a `nestedFunction` nested inside `innerFunction`.

When `nestedFunction` is executed, JavaScript follows the scope chain to resolve variable references:

1. It starts in the current scope of `nestedFunction` & finds `nestedVar`, so it uses its value.

2. It looks for `innerVar` in the current scope of `nestedFunction` but doesn't find it. It then moves up the scope chain to the outer scope of `innerFunction` & finds `innerVar` there.

3. It looks for `outerVar` in the current scope of `nestedFunction` & `innerFunction` but doesn't find it. It then moves up the scope chain to the outer scope of `outerFunction` & finds `outerVar` there.

4. It looks for `globalVar` in the current scope of `nestedFunction`, `innerFunction`, and `outerFunction` but doesn't find it. It then moves up the scope chain to the global scope and finds `globalVar` there.


The scope chain determines the order in which JavaScript searches for variables and functions. It starts from the innermost scope and moves outward until it finds the requested identifier or reaches the global scope.

In this example, `nestedFunction` has access to variables from its own scope (`nestedVar`), the outer scope of `innerFunction` (`innerVar`), the outer scope of `outerFunction` (`outerVar`), & the global scope (`globalVar`).

Note: The scope chain ensures that variables and functions are properly encapsulated and accessible based on their lexical placement in the code. It prevents naming conflicts between different scopes and allows for the creation of modular and reusable code.

Frequently Asked Questions

What happens if a variable is not found in any scope during the scope chain lookup?

If a variable is not found in any scope during the scope chain lookup, a ReferenceError is thrown, indicating that the variable is not defined.

Can a variable declared in an inner scope have the same name as a variable in an outer scope?

Yes, a variable declared in an inner scope can have the same name as a variable in an outer scope. This is known as variable shadowing, where the inner variable shadows the outer variable within its scope.

How does the scope chain affect the performance of JavaScript code?

The scope chain affects the performance of JavaScript code by determining the number of scope levels that need to be traversed to resolve a variable or function reference. A longer scope chain may result in slightly slower variable lookups, but the impact is usually negligible in most cases.

Conclusion

In this article, we discussed the concept of a scope chain in JavaScript, including global scope, local scope, lexical scope, and block scope, and how they work together. We learned that the scope chain is a hierarchical structure that determines the accessibility and visibility of variables and functions based on their lexical placement in the code. Finally, we understood that the knowledge of the scope chain is crucial for writing modular, maintainable, and efficient JavaScript code.

You can also check out our other blogs on Code360.

Live masterclass