Understanding Scope and Context in JavaScript

May 12, 202310 min read

Javascript

In JavaScript, scope and context are two different concepts that refer to different aspects of how variables and functions are accessed and used within the language.

Scope

Scope refers to the accessibility and visibility of variables, functions, and objects in some particular part of your code during runtime. It determines where variables and functions are defined and where they can be accessed. JavaScript has function scope and block scope, each with its unique characteristics.

Function Scope

Function scope refers to the accessibility of variables within a function and its nested functions. Variables declared with the var keyword and functions declarations have function scope. This means that you can access a variable or can call function anywhere in parent function regardless of where you have written it. Which is also called hoisting in JS.

If you try to access a variable before value assignment which is generally where we declare it you will give undefined value.

calculateTax(50000, true) // Accessed before declaration

function calculateTax(income, isRich) {
  console.log(tax) // it will print undefined

  if (isRich) {
    var extraTax = 4000
  } else {
    var extraTax = 0
  }

  console.log(extraTax) // 4000 as variables declares with var have function scope

  var taxRate = 0.2
  var tax = income * taxRate + extraTax
  console.log("Tax to be paid: $" + tax)
}

console.log(taxRate) // ReferenceError: taxRate is not defined
Block Scope

ES6 (ECMAScript 2015) introduced block scope with the let and const keywords. Variables declared with let and const have block scope, meaning they are accessible only within the block in which they are defined. (Typically in curly braces {})

function printNumbers() {
  for (let i = 0; i < 5; i++) {
    console.log(i)
  }
  console.log(i) // ReferenceError: i is not defined
}
printNumbers()

Here, the variable i is accessible within the for loop’s block but not outside of it.

Context

Context refers to the value of the this keyword within a function, indicating the object to which the function belongs. Understanding context is crucial when working with object-oriented JavaScript and dealing with method invocations.

const person = {
  name: "John",
  greet: function () {
    console.log("Hello, " + this.name)
  },
}

person.greet() // Hello, John

In this example, the greet function is defined within the person object. When invoked using person.greet(), the value of this refers to the person object, allowing access to its name property.

However, context can be easily misunderstood or lost, leading to unexpected results.

const person = {
  name: "John",
  greet: function () {
    setTimeout(function () {
      console.log("Hello, " + this.name) // Undefined
    }, 1000)
  },
}

person.greet()

In this case, the this inside the setTimeout callback function does not refer to the person object but to the global object (e.g., window in browsers). Consequently, this.name results in undefined. To maintain the correct context, you can use arrow functions (In which this refers to parent’s value of this) or explicitly bind the desired context using bind() or call().

Let’s resolve this issue and access the name property of the person object correctly using both approaches:

Using an Arrow Function:

Arrow functions have lexical scoping, meaning they inherit the this value from their surrounding code. You can replace the regular function inside setTimeout with an arrow function to maintain the context of the person object.

const person = {
  name: "John",
  greet: function () {
    setTimeout(() => {
      console.log("Hello, " + this.name) // Hello, John
    }, 1000)
  },
}

person.greet()

By using an arrow function, the this inside the arrow function will refer to the this value of the surrounding greet method, which is the person object.

Using Function Binding:

Another way to handle the context is by explicitly binding the desired context using the bind() method. You can bind the this value of the greet method to the anonymous function inside setTimeout.

const person = {
  name: "John",
  greet: function () {
    setTimeout(
      function () {
        console.log("Hello, " + this.name) // Hello, John
      }.bind(this),
      1000
    )
  },
}

person.greet()

Both approaches will output the desired result, printing “Hello, John” to the console after a delay of 1000 milliseconds.

Finally, Understanding scope and context in JavaScript is crucial for writing clean and error-free code, particularly when dealing with asynchronous operations. It allows you to control how variables and functions are accessed and how objects are referenced within your code, ultimately leading to more reliable and maintainable JavaScript applications.


Vishal Sharma

Hey there! This is Vishal Sharma. I reside and work at Gurgaon, India. I am a Software Engineer and primarily works with JavaScript, ReactJS and NodeJS.
LinkedIn Link

Welcome to my Javascript tech blog! Here, you'll find the latest news and updates in the world of Javascript, as well as tutorials and resources to help you improve your coding skills. From learning the basics of Javascript to advanced concepts like object-oriented programming, I post something for developers of all levels. Whether you're just starting out or you're a seasoned pro, you'll find valuable insights and information on our blog. So stay up-to-date with the latest in Javascript technology by bookmarking this page and checking back often.
Thank you for visiting!