Hamburger Icon
  • Labs icon Lab
  • Core Tech
Labs

Guided: Functions and Closures with JavaScript

This Guided Code Lab will teach you the fundamentals of functions and closures in JavaScript. You'll learn how to create and use named functions, anonymous functions, and closures while building a practical alarm clock application.

Labs

Path Info

Level
Clock icon Beginner
Duration
Clock icon 1h 3m
Published
Clock icon May 06, 2024

Contact sales

By filling out this form and clicking submit, you acknowledge our privacy policy.

Table of Contents

  1. Challenge

    Introduction

    Welcome to the lab Guided: Functions and Closures with JavaScript.

    In this lab, you'll work on implementing functions and a closure to create an alarm clock application in JavaScript with the following features:

    • Display the current time in real-time.

    • Allow users to set alarms by specifying a time and a label.

    • Show a list of set alarms.

    • Trigger an alert when an alarm time matches the current time.

    The application displays the current time, updating it every second. To set an alarm, users fill out a form by entering the desired alarm time and label. Upon form submission, the alarm is added to the list of alarms displayed on the page. When an alarm time matches the current time, an alert is triggered, notifying the user. Triggered alarms are automatically removed from the list. ---

    Familiarizing with the Program Structure

    The application is built with HTML, CSS, and JavaScript. It includes the following files:

    1. index.html: The HTML file that defines the structure and layout of the application.

    2. styles.css: The CSS file that contains the styles for the visual appearance of the application.

    3. script.js: The JavaScript file that contains the necessary functions and logic for the alarm clock functionality.

    You'll focus on the script.js file. This file is partially completed, containing the necessary DOM element definitions and some function declarations.

    You can switch to the Web Browser tab to review the application. To load your latest changes, click on the Refresh button located at the top-left corner of the tab. Initially, the application will not display the current time, and you won't be able to set alarms. However, you can explore its options and familiarize with the user interface.

    Start by examining the provided code and understanding the program structure. When you're ready, dive into the coding process. If you encounter any problems, remember that a solution directory is available for reference or to verify your code.

  2. Challenge

    Named Functions

    What are Named Functions?

    In JavaScript, named functions are a way to define reusable blocks of code that can be called with a specific name. Named functions help in organizing code, promoting reusability, and improving code readability.

    To define a named function, you use the function keyword followed by the function name, a list of parameters enclosed in parentheses (optional), and the function body enclosed in curly braces:

    function functionName(parameter1, parameter2) {
      // Function body
      // Code to be executed
    }
    

    Functions can accept parameters, which are values that the function expects to receive when it is called.

    To call a named function, you simply use the function name followed by parentheses and pass any required arguments:

    functionName(argument1, argument2);
    

    The parameters act as placeholders for the actual values (arguments) that will be passed to the function.

    For example, the following snippet defines a function named greet that takes a name parameter:

    function greet(name) {
      console.log("Hello, " + name + "!");
    }
    

    You can call the greet function with the argument John like this:

    greet("John"); // Output: Hello, John!
    

    Functions can also return a value using the return statement. The returned value can be used by the code that called the function:

    function multiply(a, b) {
      return a * b;
    }
    
    let result = multiply(2, 3);
    console.log(result); // Output: 6
    

    Here, the multiply function returns the product of a and b, which is then assigned to the result variable.

    Variables declared inside a function are only accessible within that function. This is known as function scope. It helps in avoiding naming conflicts and keeps the variables encapsulated within the function.

    function calculateArea(width, height) {
      let area = width * height;
      return area;
    }
    
    console.log(area); // Error: area is not defined
    

    In this case, area is a local variable inside the calculateArea function and cannot be accessed outside of it.

    In the next tasks, you'll implement a function to format the time shown in the application in a specific format. In the script.js file, locate the formatTime function. This function should take hours, minutes, and optionally seconds as parameters and return a formatted time string in the desired format.

  3. Challenge

    Anonymous Functions

    What are Anonymous Functions?

    Anonymous functions are functions without a name. They are often used when a function is needed as a value or as an argument to another function.

    To define an anonymous function, you use the function keyword followed by a list of parameters enclosed in parentheses (optional), and the function body enclosed in curly braces:

    function (parameter1, parameter2) {
      // Function body
      // Code to be executed
    }
    

    Anonymous functions are commonly used in combination with function expressions, where the function is assigned to a variable. This variable holds a reference to the function, allowing you to invoke the function using the variable name:

    const greet = function (name) {
      console.log("Hello, " + name + "!");
    };
    
    greet("John"); // Output: Hello, John!
    

    In this example, the variable greet holds a reference to an anonymous function that takes a name parameter and logs a greeting message.

    Anonymous functions are also used as arguments to other functions, such as setTimeout(), setInterval(), or addEventListener(). These functions expect a callback function as an argument, which is executed when a specific event occurs or after a certain time delay.

    For example, say you want to execute a function every 2 seconds using setInterval():

    setInterval(function () {
      console.log("Repeated message");
    }, 2000);
    

    In this case, an anonymous function is passed as the first argument to setInterval(). The function will be executed every 2000 milliseconds (2 seconds).

    You can also use arrow functions to define anonymous functions in a concise way. The syntax is the following:

    (parameters) => {
      // Function body
      // Code to be executed
    }
    

    If the function has only one parameter, the parentheses can be omitted:

    parameter => {
      // Function body
      // Code to be executed
    }
    

    If the function body consists of a single expression, the curly braces can be omitted, and the expression will be implicitly returned:

    (parameters) => expression
    

    Here's an example of using an arrow function with setInterval():

    setInterval(() => {
      console.log("Repeated message");
    }, 2000);
    

    In this case, an arrow function is passed as the first argument to setInterval(). Notice that the arrow function doesn't define any parameters, however, the parentheses are still required.

    In the next tasks, you'll implement the anonymous function passed to a setInterval() function used by the application. In the script.js file, locate the anonymous function passed to setInterval(), inside the initializeApp() function. This function should update the current time shown in the HTML page every second. By getting the current timestamp using new Date() and assigning it to the now variable inside the anonymous function passed to setInterval, you ensure that the current date and time are obtained at each interval (every second). This is necessary because you want to display the updated time on the web page continuously. If you placed this line outside the anonymous function, it would only get the current time once, and the displayed time would not update every second as intended.

  4. Challenge

    Closures

    What is a Closure?

    A closure is a function that has access to variables in its outer (enclosing) lexical scope, even after the outer function has returned. In other words, a closure allows a function to "remember" and access variables from its outer scope, even when the function is executed in a different scope.

    To create a closure, you simply define a function inside another function. The inner function has access to the variables and parameters of the outer function, as well as any variables defined in its own scope.

    Here's a simple example of a closure:

    function outerFunction(x) {
      let y = 10;
    
      function innerFunction() {
        console.log(x + y);
      }
    
      return innerFunction;
    }
    
    const closure = outerFunction(5);
    closure(); // Output: 15
    

    In this example, outerFunction takes a parameter x and defines a variable y. It also defines an innerFunction that accesses both x and y. The outerFunction returns the innerFunction, which is assigned to the variable closure. When closure is invoked, it still has access to the variables x and y from the outer function, even though outerFunction has already finished executing.

    Closures are often used to create private state and encapsulate data. By defining variables inside the outer function and returning an object or functions that access those variables, you can create a closure that maintains its own private data.

    Here's an example of using a closure for encapsulation:

    function createCounter() {
      let count = 0;
    
      return {
        increment: function () {
          count++;
        },
        getCount: function () {
          return count;
        }
      };
    }
    
    const counter = createCounter();
    counter.increment();
    counter.increment();
    console.log(counter.getCount()); // Output: 2
    

    In this example, createCounter is a function that returns an object with two methods: increment and getCount. The count variable is defined inside createCounter and is not directly accessible from outside the function. The increment method modifies the count variable, and the getCount method retrieves its current value. This way, the count variable is encapsulated and can only be accessed and modified through the returned object.

    Closures are also commonly used with event handlers and callbacks. When an event handler is defined inside another function, it forms a closure and can access variables from its outer scope.

    Here's an example of using a closure with an event handler:

    function setupButton(buttonId) {
      let count = 0;
    
      document.getElementById(buttonId).addEventListener('click', function () {
        count++;
        console.log(`Button clicked ${count} times`);
      });
    }
    
    setupButton('myButton');
    

    In this example, setupButton takes a buttonId parameter and sets up a click event handler for the button with that ID. The event handler is defined inside setupButton and forms a closure, allowing it to access the count variable. Each time the button is clicked, the count variable is incremented, and the current count is logged to the console.

    In the next tasks, you'll use a closure to encapsulate the alarm-related functionality and data within the createAlarmManager function, and use it in other parts of the program. In the script.js file, locate the createAlarmManager function. This function will serve as a closure to encapsulate the alarm-related data and functions. You'll move the necessary elements inside the createAlarmManager function to create a proper closure. After completing the task, the createAlarmManager function should contain the alarms array and all the related functions within its scope. The setInterval call and the returned object with addAlarm and displayAlarms functions should remain unchanged. In the script.js file, locate the initializeApp function. This function is responsible for setting up the initial state of the application and handling user interactions. You'll add the necessary code to display the initial state of alarms using the alarmManager closure. By calling alarmManager.displayAlarms() after creating an instance of the alarm manager, you ensure that the initial state of alarms is displayed correctly when the application starts. In the script.js file, locate the initializeApp function. Inside this function, find the anonymous function passed to alarmForm.addEventListener('submit', ...). This anonymous function handles the form submission event for adding a new alarm. You'll modify the code to register the new alarm using the alarmManager closure.

  5. Challenge

    Conclusion

    Congratulations on successfully completing this Code Lab!

    To try the application, navigate to the Web Browser tab. Then, click the Refresh button located in the top-left corner to load your changes. You can also click the New Tab button in the top-right corner to open the application in a new browser tab. As a test, set an alarm for the nearest minute in the future. Wait to see how an alert window is triggered at that time.

    For simplicity, the application uses an element of type <input type="time"> to allow the user to specify the alarm time. However, the display format of this element may vary depending on your browser and system locale settings. While some browsers might use a 12-hour format with AM/PM, others may use a 24-hour format. Keep this in mind when testing and using the alarm clock application. ---

    Extending the Program

    Consider exploring these ideas to further enhance your skills and expand the capabilities of the alarm clock program:

    1. Ensure consistent time format. To provide a consistent user experience across different browsers and systems, consider implementing custom input fields or using a library that allows you to enforce a certain time format (or even allow the user to choose one) for the alarm time input.

    2. Allow editing and deleting alarms. Implement functionality that allows users to edit the time and label of existing alarms, as well as delete alarms they no longer need. This will provide users with more control over their alarm settings and help keep the alarm list organized.

    3. Implement custom alarm sounds. Enhance the alarm clock by allowing users to choose custom sounds for each alarm. You can provide a selection of predefined sounds or even allow users to upload their own audio files. This will make the alarm experience more personalized and engaging.

    4. Persist alarms across page refreshes. Use the browser's local storage API to store the alarm data so that the alarms persist even if the user refreshes the page or closes the browser. This way, users won't lose their alarm settings, and the alarm clock will maintain its state across sessions.

    5. Implement recurring alarms. Extend the alarm clock to support recurring alarms that can be set to repeat daily, weekly, or on specific days of the week. This will be useful for users who have regular schedules or need to be reminded of recurring events or tasks. ---

    Related Courses on Pluralsight's Library

    If you're interested in further honing your JavaScript skills or exploring more topics, Pluralsight offers several excellent courses in the following path:

    These courses cover many aspects of JavaScript programming. Check them out to continue your learning journey in JavaScript!

Esteban Herrera has more than twelve years of experience in the software development industry. Having worked in many roles and projects, he has found his passion in programming with Java and JavaScript. Nowadays, he spends all his time learning new things, writing articles, teaching programming, and enjoying his kids.

What's a lab?

Hands-on Labs are real environments created by industry experts to help you learn. These environments help you gain knowledge and experience, practice without compromising your system, test without risk, destroy without fear, and let you learn from your mistakes. Hands-on Labs: practice your skills before delivering in the real world.

Provided environment for hands-on practice

We will provide the credentials and environment necessary for you to practice right within your browser.

Guided walkthrough

Follow along with the author’s guided walkthrough and build something new in your provided environment!

Did you know?

On average, you retain 75% more of your learning if you get time for practice.