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.