Computer scienceProgramming languagesJavaScriptBasicsFunctions

Self-invoking functions and closures

14 minutes read

One of the most prominent features of JavaScript is its ability to create closures and self-invoking functions. In this topic, we will discuss how they can be used to separate public and private data and methods, which can help to organize and structure your code.

Closures

Before we dive into the main topic, let's first take a look at the following situation:

function hello(name) {
    const text = "Hello, " + name;

    console.log(text);
}

hello("John");  // output: "Hello, John"

console.log(text)  // ReferenceError: text is not defined

In the code snippet above, we define a function called hello, which takes a string name as a parameter. We use this parameter in our text string definition, which we immediately log. After the function is defined, we call it and pass our name, which outputs what we would expect – "Hello, John." However, if we try to access text outside the function definition, we'll get a ReferenceError.

Generally speaking, the data (variables) and methods (functions) are only available within a specific scope. In our case, trying to access the text variable results in an error because this variable is only available within the scope of our hello() function. To be able to access the variable outside the scope in which it was created, we use closures.

A closure is a function that has access to variables outside of its own scope. This means that the data is still available even after the function has finished running. Closures are useful because they allow you to create private data that is not accessible from outside the function. This can be useful for protecting sensitive data or for preventing unwanted access to your code.

Let's take a look at the example above, but with closures:

function hello(name) {
    const text = "Hello, " + name;

    return function() {
        console.log(text);
    }
}



const helloJohn = hello("John");

helloJohn(); // output: "Hello, John"

console.log(text) // ReferenceError: text is not defined

The function definition is the same. What is different is that the function creates a variable text, which is accessed by the inner anonymous function. In the end, we return the anonymous function.

In this example, the inner anonymous function uses a variable outside of its scope. Nothing unusual here because the access is granted by the scoping rules in JavaScript. What we're interested in is the fact that this variable can be used even after we ran the outer function hello(), but not accessed directly (via calling console.log).

The inner function maintains the reference to its lexical environment, and our variable exists within it. For that reason, when we call hello("John"), our variable text is still available to use, and it passes "Hello, John" to console.log. That is closure, the inner function "encloses" the data in its environment and has access to it even after that data isn't available within that scope. Notice that we can't access the variable directly. It means that the variable is private to our inner function, and we can only access this variable through it.

Closures allow us to create private data, which makes them a foundation for many design patterns. One such pattern, which was introduced in ES6, is self-invoking functions.

Self-invoking functions

In the topic on defining functions, we learned that in JavaScript, there are two ways to create functions: function declaration and function expression. We mentioned that function expressions are usually a part of an assignment. In those cases, we want to save a reference to our function in a variable to call it later, potentially in many places. However, sometimes we want to execute the function once. For example, let's imagine we have an HTML div element with a class name "name", and we would like to get the data from it when the page loads. Let's examine the code below:

function load() {
    const divElement = document.querySelector(".name")
    const data = divElement.textContent

    console.log(data)
}

window.onload = load;

Our load() function gets the element that contains our data, and extracts and logs it. The function runs once when the window loads. All works perfectly. However, imagine that we also needed to load some third-party data and if a user scrolls our website down enough, we would need to load more content from our database. In theory, all three functions can be called load(), and both we and the browser would be really confused about what function we really want to execute.

This is known as name collision, and you should avoid it. One way to do that is to use self-invoking functions.

A self-invoking function, also called Immediately Invoked Function Expression or IIFE (pronounced "iffy"), is a function that runs automatically when it is defined. Let's take a look at the example above, adapted to be an IIFE:

(function () {
    const divElement = document.querySelector(".name")
    const data = divElement.textContent

    console.log(data)

})();

console.log(data); // ReferenceError: data is not defined

This expression can look complicated, as many things in JavaScript do, so let's break it down:

  1. Inside the parentheses, we have a regular anonymous function definition.
  2. The parentheses indicate an expression. In JavaScript, everything in parentheses or after an operator is an expression, and expressions return values.
  3. Here, we capitalize on the fact that JavaScript expressions return values by slapping a pair of parentheses at the end, thereby immediately invoking the function expression.
  4. After the function runs, we don't have access to the variable defined within it because we're trying to reference it outside its scope.

Since we don't give self-invoking functions a name, it helps us to keep the namespace clean. Besides that, the author's intentions behind them are easier to understand, since it is obvious that IIFEs are supposed to run only once. It makes our code self-documenting, which increases readability and makes it more understandable.

Let's consider the need for multiple load() functions, as we discussed earlier: one for retrieving data from HTML, another for loading third-party data, and a third one for fetching additional content from our database. We may have similar functions in different domains, such as DOM manipulation, external data handling, and database interactions. These functions might share some data. To organize them by their domains and differentiate between public and private data, we'll employ the module pattern.

Module pattern

ES6 also introduced "modules", which is a syntax to import and export code between different JavaScript files. This is not what we are talking about here.

The module pattern is a way of organizing your code into self-contained pieces that can be easily reused and maintained. We create a module by using a self-invoking function to create a private scope for your code, which can then be accessed through public methods and properties.

Let's study an example of this concept:

const displayElement = document.querySelector(".score");

const DisplayController = (function () {
    let _display = displayElement;
    let leftScore = 0
    let rightScore = 0

    const adjustDisplay = () => _displayController.textContent = `${leftScore} : ${rightScore}`;

    const resetDisplay = () => {
        leftScore = 0;
        rightScore = 0;
        adjustDisplay();
    }

    return {
        leftScore, rightScore, adjustDisplay, resetDisplay,
    }
})(displayElement);

DisplayController.leftScore = 2;

console.log(DisplayController.leftScore, DisplayController.rightScore); // output: 2 0

DisplayController.adjustDisplay(); // the div will show "2 : 0"

DisplayController.resetDisplay();

console.log(DisplayController.leftScore, DisplayController.rightScore); // output: 0 0

console.log(DisplayController._display.textContent); // ReferenceError: _display is not defined

It's a long example, so let's break it down point by point:

  1. Imagine we have a div element in our HTML file with a class name "score". Here, we will be manipulating its contents.
  2. Let's discuss the DisplayController function.
    1. Note that the module's name, DisplayController, starts with a capital letter. It's a convention to denote modules. Please, stick to it as it makes your code more readable.
    2. The way to create a module is to assign IIFE that returns an object to a variable. We pass our displayElement as a parameter to our self-invoking function.
    3. DisplayController has 3 variables: _display, leftScore, and rightScore, and 2 methods: adjustDisplay and resetDisplay. Notice how the variable _display has an underscore at the beginning. By convention, this is the way to denote private data or methods in JavaScript.
    4. What makes _display private is not the underscore, however, but the fact that we only return the method that uses it – adjustDisplay(). In the last line of the example, we're trying to access the variable directly, but since the object that we return doesn't have it, we can't access it directly. The only way for us to access it is through the adjustDisplay() method.
  3. Notice that we can get and set leftScore and rightScore directly. This is done intentionally for the sake of this example. However, it's a good practice to only expose the really necessary variables. In this case, we could create methods to increase or decrease the scores and make the score variables themselves private.

In the module pattern, private data and methods are kept hidden from the outside world and can only be accessed within the module. This is achieved by using closures to create a private scope for the module. Public data and methods, on the other hand, can be accessed from outside of the module and are usually used to interact with the module and to provide a public interface.

Conclusion

In this topic, we've learned about closures, self-invoking functions, and the module pattern. Closures are a way to access variables outside their scope. They are used to manipulate private data that isn't accessible outside the function. Closures are a foundation for many software patterns, one of which is self-invoking functions.

Self-invoking functions, or Immediately Invoked Function Expressions, are functions that run immediately after their definition. To create an IIFE, you need to wrap an anonymous function into parentheses and then call it by tackling another pair of parentheses at the end. Self-invoking functions are used to keep the namespace clean, increase the code readability, and introduce private data and methods. They are also used in creating a module pattern.

The module pattern is a piece of self-contained and reusable code. It is created by assigning an IIFE that returns an object to a variable. Modules are a great way to separate public and private data and methods. By convention, their names start with a capital letter, and private variables within them start with an underscore.

17 learners liked this piece of theory. 1 didn't like it. What about you?
Report a typo