1. Introduction
Scope and closures are fundamental concepts in JavaScript that play a crucial role in understanding how variables and functions are accessed and managed within a program. Having a solid understanding of scope and closures empowers developers to write efficient, modular, and maintainable code.
In this guide, we will explore the intricacies of scope and closures in JavaScript, understanding their significance and practical applications. We will examine the concept of variable visibility within different scopes, discuss the relationship between lexical scoping and closures, and explore various examples and use cases to solidify our understanding.
2. Scope in JavaScript
Scope is an essential concept in JavaScript that determines the accessibility and visibility of variables throughout your code. Let's explore how it works in JavaScript.
2.1. Definition of Scope
Scope refers to the area of your code where variables and functions are defined and can be accessed. It sets the boundaries for variable visibility and prevents naming conflicts.
2.2. Global Scope
In JavaScript, variables declared outside any function or block have global scope. They can be accessed from anywhere in your code, including within functions and blocks. However, it's important to use global variables judiciously to avoid potential issues with naming conflicts and accidental modifications.
Example 1:
var sportsEvent = 'Olympics';
function globalScopeExample() {
console.log(sportsEvent); // Accessible within functions
}
globalScopeExample(); // Output: 'Olympics'
Explanation: In this example, we declare a variable sportsEvent
with the value 'Olympics'
in the global scope. The globalScopeExample()
function can access and print the value of sportsEvent
because it is defined within the global scope.
2.3. Local Scope
Local scope restricts the visibility of variables to specific functions or blocks where they are defined. It helps encapsulate variables and prevents them from interfering with other parts of your code. JavaScript has two types of local scope: function scope and block scope.
2.3.1. Function Scope
Variables declared within a function are accessible only within that function. They are not visible outside the function or in other functions.
Example 2:
function functionScopeExample() {
var player = 'Lionel Messi';
console.log(player); // Accessible within the function
}
functionScopeExample(); // Output: 'Lionel Messi'
console.log(player); // Error: 'player' is not defined
Explanation: In this example, we declare a variable player
with the value 'Lionel Messi'
within the functionScopeExample()
function. The variable player
is accessible and can be printed within the function scope. However, if we try to access player
outside the function, we'll get an error because it is not defined in the outer scope.
2.3.2. Block Scope (introduced in ES6)
ES6 introduced block scope with the let
and const
keywords. Variables declared with let
and const
are scoped to the block in which they are defined (e.g., within if statements or loops), ensuring better control over variable visibility and avoiding issues like variable hoisting.
Example 3:
function blockScopeExample() {
if (true) {
let team = 'Real Madrid';
console.log(team); // Accessible within the block
}
console.log(team); // Error: 'team' is not defined outside the block
}
blockScopeExample();
Explanation: In this example, we declare a variable team
with the value 'Real Madrid'
within the if block. The variable team
is scoped to that block and can be accessed and printed within it. However, if we try to access team
outside the block, we'll get an error because it is not defined in the outer scope.
These examples demonstrate the concept of scope in JavaScript, highlighting the difference between global scope, function scope, and block scope, and how variables defined within each scope have their own visibility and accessibility rules.
3. Variable Visibility
Variable visibility is a crucial aspect of JavaScript programming, as it determines how and where variables can be accessed within a program. Let's explore three important concepts related to variable visibility: accessing variables within the same scope, understanding variable shadowing, and variable hoisting.
3.1. Accessing variables within the same scope
When variables are defined within the same scope, they can be accessed and used interchangeably. Here's an example to illustrate this in the context of sports:
Example 1:
function calculateScore(goals, assists) {
const goalPoints = 3;
const assistPoints = 1;
const totalScore = (goals * goalPoints) + (assists * assistPoints);
return totalScore;
}
const playerScore = calculateScore(2, 1);
console.log(playerScore); // Output: 8
In the above code snippet, the goals
and assists
parameters, as well as the goalPoints
, assistPoints
, and totalScore
variables, are all defined within the calculateScore
function's scope. This means that they can be accessed and used within that function without any issues. The totalScore
variable, which depends on goals
and assists
, can be calculated and returned successfully.
3.2. Understanding variable shadowing
Variable shadowing occurs when a variable declared in an inner scope has the same name as a variable in an outer scope. The variable in the inner scope "shadows" or takes precedence over the one in the outer scope. Consider the following example in the context of sports:
Example 2:
const teamName = "Barcelona";
function shadowingExample() {
const teamName = "Real Madrid";
console.log(teamName); // Output: "Real Madrid"
}
shadowingExample();
console.log(teamName); // Output: "Barcelona"
In the above code snippet, there are two variables named teamName
. The outer teamName
is declared globally with a value of "Barcelona". Inside the shadowingExample
function, a new teamName
variable is declared with a value of "Real Madrid". When we log the value of teamName
inside the function, it outputs "Real Madrid", indicating that the inner variable shadows the outer one. Outside the function, the value of the global teamName
variable remains unchanged as "Barcelona".
3.3. Variable hoisting and its impact on visibility
Variable hoisting is a JavaScript behavior where variable declarations are moved to the top of their containing scope during the compilation phase. However, only the declarations are hoisted, not the initializations. Take a look at the following example in the context of sports:
Example 3:
function hoistingExample() {
console.log(playerName); // Output: undefined
var playerName = "Lionel Messi";
console.log(playerName); // Output: "Lionel Messi"
}
hoistingExample();
In the above code snippet, even though the playerName
variable is logged before it's declared and initialized, it doesn't throw an error. This is because of hoisting. During hoisting, the variable declaration var playerName;
is moved to the top of the hoistingExample
function, resulting in the first console.log()
statement printing undefined
. Then, the variable is assigned the value "Lionel Messi"
, and the second console.log()
statement outputs the assigned value.
Note: Gaining a grasp of these fundamental concepts regarding variable visibility in JavaScript is essential for comprehending how variables are accessed and behave within various scopes.
4. Lexical Scoping
4.1. Explaining Lexical Scoping in JavaScript
In JavaScript, lexical scoping refers to the way variables are resolved at the time of their declaration based on the nested structure of functions and blocks. Lexical scoping means that variables are accessible within the scope in which they are defined, as well as any nested scopes. This allows variables to be accessed by functions and blocks defined within their parent scope.
Example:
function outer() {
var tennisPlayer = 'Roger Federer';
function inner() {
console.log(tennisPlayer);
}
inner();
}
outer();
Explanation: In the code above, the variable tennisPlayer
is defined in the outer scope of the outer
function. When the inner
function is called, it has access to the tennisPlayer
variable because of lexical scoping. The output of this code will be 'Roger Federer'
.
4.2. Closures and Their Relationship to Lexical Scoping
Closures are closely related to lexical scoping in JavaScript. A closure is created when an inner function retains access to variables in its outer scope, even after the outer function has finished executing. This behavior is possible because of the way JavaScript handles lexical scoping.
Example:
function outer() {
var nbaPlayer = 'LeBron James';
function inner() {
console.log(nbaPlayer);
}
return inner;
}
var closureFunction = outer();
closureFunction();
Explanation: In this code, the outer
function returns the inner
function. When outer
is called and assigned to closureFunction
, the variable nbaPlayer
is still accessible to closureFunction
due to the closure created. As a result, calling closureFunction()
prints 'LeBron James'
to the console.
4.3. Benefits of Lexical Scoping and Closures
4.3.1. Data Privacy and Encapsulation
Lexical scoping and closures allow us to create private variables and functions. By enclosing variables within a scope and returning inner functions that have access to those variables, we can control the visibility of data. This helps in achieving data privacy and encapsulation, preventing unwanted modifications or access from outside the scope.
4.3.2. Callback Functions and Event Handlers
Closures are often used in JavaScript for callback functions and event handling. When an event occurs, such as a button click, the associated callback function is executed. Closures enable the callback function to access variables in its surrounding scope, allowing it to work with the necessary data without exposing it globally.
4.3.3. Asynchronous Operations
Closures are essential in handling asynchronous operations in JavaScript. When dealing with timers, AJAX requests, or promises, closures ensure that the necessary data is preserved and accessible when the asynchronous operation completes. This helps maintain the integrity of the program's logic, as the closure carries the relevant state information until it is needed.
Note: Lexical scoping and closures offer powerful capabilities that enable you to write more efficient, modular, and maintainable JavaScript code. These concepts play a crucial role in managing variable visibility, ensuring data privacy, and effectively handling complex scenarios involving asynchronous operations and event-driven programming.
5. Creating Closures
5.1 Defining closures in JavaScript
Closures are an important concept in JavaScript that allow functions to retain access to variables from their outer scope, even after the outer function has finished executing. In simpler terms, a closure is created when an inner function "remembers" its lexical environment, including the variables and functions within its scope.
5.2 Practical examples of closures
5.2.1. Preserving football player statistics
Example:
function createPlayer(name, goals) {
let playerName = name;
let playerGoals = goals;
return {
getName: function() {
return playerName;
},
getGoals: function() {
return playerGoals;
},
scoreGoal: function() {
playerGoals++;
console.log(playerName + " scored a goal! Total goals: " + playerGoals);
}
};
}
const player1 = createPlayer("Lionel Messi", 20);
console.log(player1.getName()); // Output: "Lionel Messi"
console.log(player1.getGoals()); // Output: 20
player1.scoreGoal(); // Output: "Lionel Messi scored a goal! Total goals: 21"
In this example, the createPlayer
function creates a player object with a name and a goal count. The inner functions getName
, getGoals
, and scoreGoal
have access to the playerName
and playerGoals
variables through closure. This allows us to encapsulate the player's data and provide controlled access to it.
5.2.2. Tracking football match statistics
function createMatch() {
let homeTeamScore = 0;
let awayTeamScore = 0;
return {
homeTeamGoal: function() {
homeTeamScore++;
console.log("Home Team scored! Total goals: " + homeTeamScore);
},
awayTeamGoal: function() {
awayTeamScore++;
console.log("Away Team scored! Total goals: " + awayTeamScore);
},
getScore: function() {
return "Home Team: " + homeTeamScore + ", Away Team: " + awayTeamScore;
}
};
}
const match1 = createMatch();
match1.homeTeamGoal(); // Output: "Home Team scored! Total goals: 1"
match1.awayTeamGoal(); // Output: "Away Team scored! Total goals: 1"
console.log(match1.getScore()); // Output: "Home Team: 1, Away Team: 1"
In this example, the createMatch
function creates a match object with variables to track the scores of the home team and away team. The inner functions homeTeamGoal
, awayTeamGoal
, and getScore
have access to these variables through closure, allowing us to manipulate and retrieve the match statistics.
5.2.3. Managing football team lineups
function createTeam() {
let lineup = [];
return {
addPlayer: function(player) {
lineup.push(player);
console.log(player + " added to the lineup.");
},
removePlayer: function(player) {
const index = lineup.indexOf(player);
if (index !== -1) {
lineup.splice(index, 1);
console.log(player + " removed from the lineup.");
}
},
getLineup: function() {
return lineup;
}
};
}
const team1 = createTeam();
team1.addPlayer("Lionel Messi"); // Output: "Lionel Messi added to the lineup."
team1.addPlayer("Cristiano Ronaldo"); // Output: "Cristiano Ronaldo added to the lineup."
console.log(team1.getLineup()); // Output: ["Lionel Messi", "Cristiano Ronaldo"]
team1.removePlayer("Lionel Messi"); // Output: "Lionel Messi removed from the lineup."
console.log(team1.getLineup()); // Output: ["Cristiano Ronaldo"]
In this example, the createTeam
function creates a team object with a lineup array. The inner functions addPlayer
, removePlayer
, and getLineup
have access to the lineup
variable through closure, enabling us to manage the team's lineup by adding and removing players.
Note: These examples demonstrate how closures can be used in the context of football to preserve player statistics, track match scores, and manage team lineups. Closures offer powerful capabilities for encapsulating data and providing controlled access to it, making them valuable in various aspects of sports-related JavaScript applications.
6. Memory Management in Closures
6.1. Understanding the Closure's Scope Chain and Garbage Collection
In JavaScript, closures have a powerful feature known as the closure scope chain. The scope chain is a mechanism that allows a closure to access variables from its outer scope even after the outer function has finished executing. However, this can also lead to potential memory management issues if not handled properly.
Example:
function outerFunction() {
var ligue = 'Ligue 1 - France';
function innerFunction() {
console.log(ligue);
}
return innerFunction;
}
var closure = outerFunction();
closure(); // Outputs: "Ligue 1 - France"
Explanation: In this example, the innerFunction
has access to the ligue
variable even though it has finished executing. This is because the closure retains a reference to the variables it needs, keeping them alive in memory.
Note: It's important to be cautious with memory management when dealing with closures. If a closure retains unnecessary references, it can cause memory leaks.
6.2. Memory Leaks and Closure Retention
Memory leaks can occur when closures unintentionally retain references to objects that are no longer needed, preventing their garbage collection. This can lead to increased memory usage and potentially degrade the performance of your application.
Let's consider an example to illustrate a memory leak caused by closures:
function createClosure() {
var players = ['Neymar', 'Mbappe', 'Di Maria']; // Simulating an array of players
return function() {
console.log(players);
}
}
var closure = createClosure();
closure(); // The array of players is still retained in memory
Explanation: In this example, the closure closure
retains a reference to the players
array, even though it's no longer needed. As a result, the array of players remains in memory, leading to a memory leak.
6.3. Best Practices for Managing Memory in Closures
To manage memory effectively when using closures, consider the following best practices:
6.3.1. Release Unnecessary References
Make sure that closures only retain references to variables that are actually needed. Avoid retaining references to large objects or unnecessary data to prevent memory leaks.
function createClosure() {
var players = ['Neymar', 'Mbappe', 'Di Maria'];
return function() {
console.log('Perform some operations with players');
players = null; // Release the reference to the array of players
}
}
var closure = createClosure();
closure(); // The array of players can now be garbage collected
6.3.2. Use IIFE for Temporary Closures
Immediately Invoked Function Expressions (IIFE) can be used when you need a closure for a specific task and don't require long-term retention. IIFE allows you to execute the function immediately and release its memory afterward.
(function() {
var tempData = 'Temporary data';
// Perform some operations with tempData
console.log(tempData);
})(); // IIFE, the closure is immediately released after execution
6.3.3 Be Mindful of Event Handlers
When attaching event handlers, ensure that closures used within event listeners do not inadvertently retain references to DOM elements. Remove event listeners or clear the closures when the elements are no longer needed to prevent memory leaks.
var button = document.getElementById('myButton');
button.addEventListener('click', function() {
console.log('Button clicked');
// Remove event listener or nullify the closure if the button is no longer needed
button.removeEventListener('click', arguments.callee);
});
Note: Remember, managing memory in closures is crucial for long-running applications or scenarios involving large data sets.
7. Advanced Topics
In JavaScript, Immediately Invoked Function Expressions (IIFE) are powerful constructs that allow you to execute a function immediately after it is defined. They are commonly used to create a private scope and prevent variable pollution in the global scope. Let's explore three examples of IIFE and understand their significance.
Example 1:
(function() {
// Code executed within the IIFE's private scope
var championship = "Champions League";
console.log(championship);
})();
Explanation: In this example, we define an anonymous function and immediately invoke it using parentheses at the end (function() {...})();
. The function creates a private scope where we can declare variables, functions, and execute code. In this case, we define a variable championship
with the value "Champions League" and log it to the console. The advantage of using an IIFE here is that the championship
variable is contained within the function's scope and does not pollute the global scope.
Example 2:
var result = (function(homeTeam, awayTeam) {
// Code executed within the IIFE's private scope
return homeTeam + " vs " + awayTeam;
})("Real Madrid", "Barcelona");
console.log(result); // Output: "Real Madrid vs Barcelona"
Explanation: In this example, we define an IIFE that takes two parameters homeTeam
and awayTeam
. Within the IIFE, we concatenate the two team names and return the result. By immediately invoking the IIFE with the arguments ("Real Madrid", "Barcelona")
, the returned value is assigned to the variable result
. This approach allows us to encapsulate the concatenation logic within the IIFE and obtain the result in a concise manner.
Example 3:
var teamModule = (function() {
var team = "";
return {
setTeam: function(name) {
team = name;
},
getTeam: function() {
return team;
}
};
})();
teamModule.setTeam("Liverpool");
console.log(teamModule.getTeam()); // Output: "Liverpool"
Explanation: In this example, we create an IIFE that defines a private variable team
and returns an object with two methods: setTeam
and getTeam
. The setTeam
method modifies the team
variable by assigning a new value, while the getTeam
method retrieves its value. By invoking the IIFE and assigning it to the variable teamModule
, we effectively create a module with encapsulated state and behavior related to a sports team.
7.2. Module Patterns Using Closures
When you work with JavaScript, module patterns using closures are commonly employed to encapsulate related functionality, promote code organization, and achieve information hiding. Here are three examples of module patterns.
Example 1:
var championsLeagueModule = (function() {
var champion = "";
return {
setChampion: function(team) {
champion = team;
},
getChampion: function() {
return champion;
}
};
})();
championsLeagueModule.setChampion("Bayern Munich");
console.log(championsLeagueModule.getChampion()); // Output: "Bayern Munich"
Explanation: In this example, we create a module using an IIFE. The module defines a private variable champion
and returns an object with two methods: setChampion
and getChampion
. The setChampion
method allows us to set the champion team by assigning a value to the champion
variable, while the getChampion
method retrieves its value. This module pattern allows us to encapsulate the champion team-related functionality and prevent direct access to the champion
variable.
Example 2:
var europaLeagueModule = (function() {
var teams = [];
return {
addTeam: function(team) {
teams.push(team);
},
removeTeam: function(index) {
teams.splice(index, 1);
},
getTeams: function() {
return teams;
}
};
})();
europaLeagueModule.addTeam("Arsenal");
europaLeagueModule.addTeam("Sevilla");
console.log(europaLeagueModule.getTeams()); // Output: ["Arsenal", "Sevilla"]
europaLeagueModule.removeTeam(1);
console.log(europaLeagueModule.getTeams()); // Output: ["Arsenal"]
Explanation: In this example, we create a module that manages a list of teams for the Europa League using an IIFE. The module maintains a private array teams
and provides methods to add teams, remove teams by index, and retrieve the list of teams. By invoking the module's methods, we can interact with the encapsulated teams
array while ensuring that it remains protected and inaccessible from external code.
Example 3:
var playerModule = (function() {
var players = [];
return {
addPlayer: function(player) {
players.push(player);
},
removePlayer: function(index) {
players.splice(index, 1);
},
getPlayers: function() {
return players;
}
};
})();
playerModule.addPlayer("Lionel Messi");
playerModule.addPlayer("Cristiano Ronaldo");
console.log(playerModule.getPlayers()); // Output: ["Lionel Messi", "Cristiano Ronaldo"]
playerModule.removePlayer(1);
console.log(playerModule.getPlayers()); // Output: ["Lionel Messi"]
Explanation: In this example, we create a module that manages a list of players using an IIFE. The module maintains a private array players
and provides methods to add players, remove players by index, and retrieve the list of players.
By invoking the module's methods, we can manipulate and retrieve the player list while ensuring that the players
array remains encapsulated within the module's scope.
7.3. Partial Application and Currying
Partial application and currying are techniques used in functional programming to create new functions by pre-filling some arguments of an existing function. These techniques allow for code reusability and can enhance flexibility in function composition. Let's explore three examples to understand how they work.
Example 1:
function createMatchReport(reporter, league, homeTeam, awayTeam) {
console.log(reporter + " reporting on " + league + ": " + homeTeam + " vs " + awayTeam);
}
var championsLeagueReporter = createMatchReport.bind(null, "John Doe", "Champions League");
championsLeagueReporter("Real Madrid", "Barcelona"); // Output: "John Doe reporting on Champions League: Real Madrid vs Barcelona"
championsLeagueReporter("Liverpool", "Manchester City"); // Output: "John Doe reporting on Champions League: Liverpool vs Manchester City"
Explanation: In this example, we have a createMatchReport
function that takes four arguments: reporter
, league
, homeTeam
, and awayTeam
. By using the bind
method, we create a new function championsLeagueReporter
where the reporter
and league
parameters are pre-filled with the values "John Doe" and "Champions League", respectively. Now, whenever we call championsLeagueReporter
and provide homeTeam
and awayTeam
arguments, it will invoke the createMatchReport
function with the pre-filled values.
Example 2:
function calculateScore(homeTeam, awayTeam, goals) {
console.log(homeTeam + " " + goals + " - " + awayTeam);
}
function curry(func) {
return function curried(homeTeam) {
return function(awayTeam) {
return function(goals) {
func(homeTeam, awayTeam, goals);
};
};
};
}
var championsLeagueCurried = curry(calculateScore)("Real Madrid")("Barcelona");
championsLeagueCurried(3); // Output: "Real Madrid 3 - Barcelona"
championsLeagueCurried(2); // Output: "Real Madrid 2 - Barcelona"
Explanation: In this example, we have a calculateScore
function that logs the score of a match. The curry
function is a utility function that transforms a function into a curried version. It takes a function func
and returns a new function that allows partial application. By applying curry
to calculateScore
, we create a curried version championsLeagueCurried
. We then use partial application to pre-fill the homeTeam
and awayTeam
arguments as "Real Madrid" and "Barcelona", respectively. Finally, we invoke championsLeagueCurried
with the goals
argument to log the score of the match.
Example 3:
function calculatePrice(basePrice, taxRate, discount, quantity) {
var totalPrice = basePrice * quantity;
totalPrice += totalPrice * (taxRate / 100);
totalPrice -= discount;
console.log("Total Price: $" + totalPrice.toFixed(2));
}
function partial(func, ...partialArgs) {
return function(...args) {
return func(...partialArgs, ...args);
};
}
var calculatePriceWithTax = partial(calculatePrice, 100, 10, 5);
calculatePriceWithTax(2); // Output: "Total Price: $222.00"
calculatePriceWithTax(5); // Output: "Total Price: $525.00"
Explanation: In this example, we have a calculatePrice
function that calculates the total price of a product based on various factors. The partial
function is a utility function that allows partial application by pre-filling some arguments. By applying partial
to calculatePrice
with the arguments (100, 10, 5)
, we create a new function calculatePriceWithTax
where the basePrice
, taxRate
, and discount
arguments are pre-filled. Now, whenever we call calculatePriceWithTax
and provide the quantity
argument, it will invoke the calculatePrice
function with the pre-filled values and calculate the total price.
Note: These advanced topics in JavaScript, including Immediately Invoked Function Expressions (IIFE), module patterns using closures, and partial application/currying, provide powerful techniques to enhance code organization, achieve encapsulation, and promote functional programming principles.
8. Exercises: Scope and Closures in JavaScript
Exercise 1:
Write a function called `createScoreboard` that returns a new function. The returned function should increment a score variable defined in the outer scope each time it is invoked. Test the functionality of `createScoreboard` by creating multiple scoreboards and incrementing their scores individually.
Exercise 2:
Create a function called `secretTeam` that takes a string as an argument. Within the function, define a variable `teamName` and a nested function `revealTeam`. The `revealTeam` function should log the `teamName` variable to the console. Test the behavior of `secretTeam` by invoking the `revealTeam` function from outside its scope.
Exercise 3:
Implement a function called `makeFormula1Race` that takes a number as an argument and returns a new function. The returned function should accept another number as an argument and calculate the total race time by multiplying it with the initial number passed to `makeFormula1Race`. Test the functionality of `makeFormula1Race` by creating multiple race functions with different initial numbers and using them to calculate race times.
Exercise 4:
Create a function called `outerPlayer` that defines a variable `goals` and a nested function `innerPlayer`. Within `innerPlayer`, define another variable `assists` and log the sum of `goals` and `assists` to the console. Invoke `outerPlayer` multiple times with different values for `goals` and observe the output of `innerPlayer`.
8. Solutions: Scope and Closures in JavaScript
Exercise 1:
function createScoreboard() {
let score = 0;
return function() {
score++;
console.log("Score: " + score);
};
}
const tennisScoreboard = createScoreboard();
const footballScoreboard = createScoreboard();
tennisScoreboard(); // Score: 1
tennisScoreboard(); // Score: 2
footballScoreboard(); // Score: 1
Explanation: In this exercise, we define a function createScoreboard
that returns a new function. The inner function, which is returned, has access to the score
variable defined in the outer scope due to closure.
When createScoreboard
is called, it creates a separate score
variable for each scoreboard. Each time the inner function is invoked, it increments the corresponding score
variable and logs the updated score to the console.
We create two scoreboards: tennisScoreboard
and footballScoreboard
. Invoking tennisScoreboard()
twice increments the score by 1 each time. Similarly, invoking footballScoreboard()
increments the score for the football scoreboard. As a result, we get the output: Score: 1
, Score: 2
, Score: 1
.
Exercise 2:
function secretTeam(teamName) {
function revealTeam() {
console.log("Team Name: " + teamName);
}
return revealTeam;
}
const handball = secretTeam("Handball Team");
const formula1 = secretTeam("Formula 1 Team");
handball(); // Team Name: Handball Team
formula1(); // Team Name: Formula 1 Team
Explanation: In this exercise, we define a function secretTeam
that takes a teamName
parameter. Inside this function, we define a nested function revealTeam
that has access to the teamName
variable due to closure.
The secretTeam
function returns the revealTeam
function, which can be invoked from outside its scope. When we invoke handball()
, it logs the value of the teamName
variable passed during the creation of the handball
function, resulting in Team Name: Handball Team
. Similarly, invoking formula1()
logs Team Name: Formula 1 Team
to the console.
Exercise 3:
function makeFormula1Race(initialMultiplier) {
return function(raceTime) {
return initialMultiplier * raceTime;
};
}
const race1 = makeFormula1Race(2);
const race2 = makeFormula1Race(3);
console.log(race1(5)); // 10
console.log(race2(7)); // 21
Explanation: In this exercise, we define a function makeFormula1Race
that takes an initialMultiplier
parameter. It returns a new function that accepts a raceTime
parameter. The inner function calculates the total race time by multiplying the initialMultiplier
with the raceTime
.
We create two race functions: race1
and race2
, with different initial multipliers. When we invoke race1(5)
, it multiplies the initial multiplier of 2 with the race time of 5, resulting in a total race time of 10. Similarly, invoking race2(7)
multiplies the initial multiplier of 3 with the race time of 7, resulting in a total race time of 21.
Exercise 4:
function outerPlayer(goals) {
return function(assists) {
console.log("Total Points: " + (goals + assists));
};
}
const player1 = outerPlayer(10);
const player2 = outerPlayer(5);
player1(3); // Total Points: 13
player2(2); // Total Points: 7
Explanation: In this exercise, we define a function outerPlayer
that takes a goals
parameter. It returns a new function that accepts an assists
parameter. The inner function calculates the total points by adding the goals and assists together.
We create two player functions: player1
and player2
, with different goal values. When we invoke player1(3)
, it adds the goals value of 10 with the assists value of 3, resulting in a total of 13 points. Similarly, invoking player2(2)
adds the goals value of 5 with the assists value of 2, resulting in a total of 7 points.
The console output for each invocation displays the total points for the player.
9. Encouragement.
Thank you for investing your time in reading this guide! I trust that you have found it informative and beneficial in enhancing your comprehension of JavaScript Scope and closures.
Comments
Post a Comment