Lets first understand the lexical scope to understand the concept of function scope and closures in javascript
In the context of programming and scope, the term “lexical” refers to the concept of how the scope of variables is determined based on the physical structure (the lexical structure) of the code. Lexical scope, also known as static scope or compile-time scope, is determined during the lexical analysis phase of the code, which is typically done by the compiler or interpreter.
In a lexically scoped language like JavaScript, the scope of a variable is determined by where it is declared in the source code. The scope is defined by the block structure (such as functions or nested functions) in which the variable is declared. This means that variables declared inside a function are accessible only within that function and any nested functions inside it. The lexical scope allows inner functions to access variables from their outer functions.
Here’s an example to illustrate the concept of lexical scope:
function outer() {
var x = 10;
function inner() {
var y = 5;
console.log(x + y);
}
inner();
}
outer();
In the above example, the variable x
is declared in the outer function, and the variable y
is declared in the inner function. The inner function can access the x
variable because it is lexically within the scope of the outer function. This lexical relationship allows the inner function to access and use the variable x
even though it is defined in a different scope.
The lexical scope concept is important as it helps in understanding how variables are accessed and resolved in nested functions or blocks. It allows for encapsulation and control over variable visibility and accessibility within the code.
Function scope and closures are important concepts in JavaScript that relate to how variables are accessed and preserved within functions.
- Function Scope:
Function scope refers to the visibility and accessibility of variables within a function. In JavaScript, variables declared inside a function using thevar
,let
, orconst
keywords are only accessible within that function and its nested functions (lexical scope). They are not accessible outside the function. Here’s an example:
function outer() {
var x = 10; // x is only accessible within the outer function
function inner() {
var y = 5; // y is only accessible within the inner function
console.log(x + y); // Output: 15
}
inner();
}
outer();
console.log(x); // Error: x is not defined
console.log(y); // Error: y is not defined
In the above example, the variables x
and y
have function scope. They are accessible within their respective functions but not outside of them.
In the given code, the outer()
function does not have access to the variable y
. the variable y
is declared within the inner()
function, which creates a new scope. This means that the variable y
is only accessible within the scope of the inner()
function and not within the scope of the outer()
function or any other outside functions.
Conversely, the inner()
function has access to the variables declared in its outer scope, which includes the x
variable defined in the outer()
function. So, when the inner()
function is called from within the outer()
function, it can access and use the x
variable. However, the outer()
function does not have direct access to variables declared within the inner()
function, such as y
. It is important to note that inner functions can access variables from their outer scope, but the reverse is not true.
function outer() {
var x = 10;
function inner() {
var y = 5;
console.log(x + y); // Accessible within inner()
}
inner();
console.log(x); // Accessible within outer()
console.log(y); // Error: y is not defined (not accessible within outer())
}
outer();
In this code, the console.log(x)
statement within the outer()
function correctly prints the value of x
. However, the console.log(y)
statement throws an error because the y
variable is not accessible within the scope of the outer()
function.
- Closures:
Closures are a powerful concept that allows inner functions to access variables from their outer (enclosing) functions even after the outer function has finished executing. In other words, a closure is the combination of a function and its surrounding lexical environment (scope chain). Here’s an example to illustrate closures:
function outer() {
var name = "John";
function inner() {
console.log("Hello, " + name + "!");
}
return inner;
}
var greet = outer();
greet(); // Output: Hello, John!
In the above example, the inner function inner()
is returned from the outer()
function. When outer()
is invoked, it creates a closure by preserving the name
variable within the lexical environment of inner()
. Even after outer()
has finished executing, the closure allows inner()
to access and use the name
variable.
Closures are particularly useful when dealing with asynchronous operations, event handlers, and maintaining private data in JavaScript. They provide a way to encapsulate data and behavior within a function and keep it accessible even when the function is no longer in scope.
function counter() {
var count = 0;
function increment() {
count++;
console.log(count);
}
function decrement() {
count--;
console.log(count);
}
return { increment, decrement };
}
var counterObj = counter();
counterObj.increment(); // Output: 1
counterObj.increment(); // Output: 2
counterObj.decrement(); // Output: 1
In the above example, the counter()
function returns an object with two methods: increment()
and decrement()
. Each method has access to the count
variable due to the closure created by the counter()
function. This allows the count
variable to be incremented or decremented each time the corresponding method is called.
Closures are an essential and powerful aspect of JavaScript, allowing for data encapsulation and preserving the state of variables within functions even after they’ve finished executing.