Lesson Weekend

Note: You are not expected to use closures on Friday's code review. This lesson is a further exploration, which means you can explore closures as much or as little as you want this week.

Note: You are not expected to use closures on Friday's code review. This lesson is a further exploration, which means you can explore closures as much or as little as you want this week.

Let’s take a look at a more advanced JavaScript concept: closures. A closure is simply an inner function that has access to variables from an outer function. In a very general sense, every function in JavaScript is a closure once it’s created because it has access to the variables inside the function in addition to any variables that are globally scoped.

Experienced JavaScript developers use closures for creating private data, functional programming (a type of programming that focuses on functions instead of objects), and making code more reusable. While you’re not expected to have a deep understanding of closures yet, you’re now at the stage of your development where you can start exploring more advanced concepts, even if they aren’t fully clear yet. If you’re interested in JavaScript development, then closures will be an essential part of your journey. Knowing about closures can help you better understand the code others are writing and can also be a great talking point in an interview as well.

Let’s start by creating a basic example of a closure and then add one to our hungry bear application. We’ll create a welcome() function with a custom salutation and name.

function welcome(salutation) {
  return function(yourName) {
    return `${salutation}! Nice to meet you, ${yourName}!`
  }
}

The welcome() function is the outer function. It takes an argument (a specific salutation) and then returns an anonymous inner function. (When a function is unnamed, it’s anonymous.) The inner function also takes an argument: yourName.

When the anonymous inner function is called, it will return a string that includes both the salutation and yourName. In other words, the inner function has access both to the variable declared inside it (yourName) and the one declared in the outer function (salutation).

We’ve now created a closure, but how do we use it? We need to assign it to a variable so it can be used elsewhere:

let heyThere = welcome(“Hey there”);

So what exactly is happening? We’re calling welcome(“Hey there”); and returning the inner function, which is stored inside the variable heyThere. We can see this by checking the value of heyThere in the console:

function(yourName) {
    return `${salutation}! Nice to meet you, ${yourName}!`
  }

heyThere is just referencing the function. To actually call the function, we need to add (). So let’s call heyThere() in the console and see what happens:

"Hey there! Nice to meet you, undefined!"

Let’s set aside the undefined for a moment. The key takeaway here is that the inner function still has access to salutation, which was declared in the outer function. But yourName is still undefined. That’s because the inner function takes yourName as an argument, so we need to pass it in. So let’s try heyThere(“Joe”): "Hey there! Nice to meet you, Joe!"

As we can see, our inner function has access to variables in both the inner and outer function. Even after the outer function has been called, the inner function continues to have this access. This is where the term closure comes from: the ability of the function to close over the variables, keeping them in scope. In many languages (C being an example), variables are destroyed as soon as the outer function is completed. As a result, these languages can’t use this powerful functionality.

We can use closures for many things, including making our code reusable. Now that we’ve seen our first example of a closure, let’s add some additional functionality to our hungry bear application. Let’s say we want to make our game a little more complicated; a hungry bear can eat a small, medium or large meal, and each meal increases the foodLevel by a different amount. While our game is still very rudimentary, you can begin to see how this game could take shape and grow bigger; imagine our protagonist running around the forest, foraging for food and items to keep the hungry bear happy.

We could add this feature in our application by adding three different functions, one for each size of meal. However, this code would get repetitive quickly, especially if we start adding even more meal sizes and ways to change a bear’s foodLevel. We could also put it all into one function and use conditionals, but this function would quickly get cumbersome as our application grows bigger.

So let’s try using a closure instead. There are better ways to implement this particular feature, but the point of this exercise is to see how we can create a reusable function and get more practice with closures as a result.

To show how closures are useful, we’ll refactor our application so that we’re not using ES6 classes. Instead, we’ll create a bear variable that stores information about the bear object, including its properties and prototypes. We experimented with this way of instantiating objects in the Arrow Functions lesson. Here’s what our new object looks like:

js/hungrybear.js
export let bear = {
  foodLevel: 10,
  setHunger: function() {
    const hungerInterval = setInterval(() => {
      this.foodLevel--;
      if (this.didYouGetEaten() == true) {
        clearInterval(hungerInterval);
        return "You got eaten!";
      }
    }, 1000);
  },
  didYouGetEaten: function() {
    if (this.foodLevel > 0) {
      return false;
    } else {
      return true;
    }
  },
  feed: function(amount) {
    let that = this;
    return function(food) {
      that.foodLevel += amount
      return `The bear ate the ${food}! Food level goes up ${amount}!`
    }
  }
};

We’re now storing all of our functions inside the bear variable instead of a class. One advantage of this is that these prototypes are all tightly encapsulated within this single variable now. That wouldn’t be so helpful if we needed to make many bears in our application, but there’s only one, so this works just as well as using classes.

We also updated our feed function so that it can be used as a function factory, which means we can use it (along with closures) to quickly create new functions. We haven’t created the closures yet; let’s update our tests to ensure that the code above is working correctly.

We only need to make a few small changes to the tests to get them passing:

spec/hungrybear-spec.js
import { bear } from './../js/hungrybear.js';

describe('HungryBear', function() {
  let fuzzy = bear;

  beforeEach(function() {
    jasmine.clock().install();
    fuzzy.foodLevel = 10;
    fuzzy.name = "Fuzzy";
    fuzzy.setHunger();
  });

Instead of importing FuzzyBear, we now import bear instead. Classes aren’t the only thing we can export and import. We can also do the same with variables or functions. In this case, we’re just importing the bear variable, which contains our bear object.

Next, we need to let fuzzy = bear instead of instantiating a new HungryBear object. We also need to actually set the name property inside our beforeEach() block since we no longer pass a name into a constructor.

There’s one last thing we need to do: we have a test that uses the feed() function, but we’ve updated the function, so let’s comment that test out (or remove it entirely). All of our other tests should be fully passing now.

We’re ready to write a new test! We’ll have a method called eatSmall() that could be called on our bear object. It should increase a bear’s foodLevel by 5. That food could potentially be anything: blueberries, raspberries and honey are all examples. Here’s our test:

spec/hungrybear-spec.js
  it('should return that the bear ate blueberries and the food level should go up 5', function() {
    expect(fuzzy.eatSmall("blueberries")).toEqual("The bear ate the blueberries! Food level goes up 5!");
    expect(fuzzy.foodLevel).toEqual(15);
  });

Let’s make sure the test fails. We should get the following error: TypeError: fuzzy.eatSmall is not a function. Let’s update our business logic.

js/hungrybear.js
export let bear = {
  ...
};
...
bear.eatSmall = bear.feed(5);

All we need is a single line of code: bear.eatSmall = bear.feed(5). Here, we create a new property on the bear called eatSmall. We’ll store the value of bear.feed(5) inside that property. Now the value of bear.eatSmall can basically be boiled down to the code snippet below:

function(food) {
      this.foodLevel += 5
      return `The bear ate the ${food}! Food level goes up 5!`
    }

Our inner function is storing two pieces of information from the outer function: the value of this and the specific amount that the bear’s foodLevel will increase. Our new tests should be passing now.

So what makes this function factory so potentially useful? Well, look how easily we can create new and unique functions from it:

bear.eatSmall = bear.feed(5);
bear.eatMedium = bear.feed(10);
bear.eatLarge = bear.feed(15);
bear.eatYuck = bear.feed(-10);
bear.eatPowerUp = bear.feed(50);
bear.eatSpecialBonus = bear.feed(100);
bear.eatWeirdThing = bear.feed(Math.floor((Math.random() * 20) + 1));

Our function factory has given us a huge amount of flexibility to create new functions that do slightly different things. We could even have altered our function factory to affect, say, a sleepLevel or an annoyanceLevel, giving the factory even more flexibility.

In this lesson, we’ve learned how to use closures to create function factories. This is just one use case for closures; they can also be used for encapsulating private data and functional programming. We also saw how we could refactor our code so that we don’t use ES6 classes at all; instead, we gave prototypes to one object, not a class of objects. There’s an important takeaway here: while ES6 classes are useful and can make your code more readable, they aren’t a one-size-fits-all solution for all JavaScript coding problems. JavaScript is an extremely flexible language, which can make it both liberating and frustrating to learn.