Another real-world demonstration of Javascript closure to help you take another step forward in your learning journey.

So you’ve become aware of a monster in Javascript called closure. There’re hundreds of quality guides on this topic, but there can never be enough practical examples. Hence, in this article, I will provide a real-world example of Javascript closure as applied to event listeners.

It is safe to say that no matter how much you have read about closure, the light bulb in your head won’t shine bright until you’ve practiced thousands of times.

However, the problem with most instructions is that their examples are too academic, not very practical, and largely taken out of context.

Worse, it’s hard to recreate them on a code playground for your learning purpose.

For that reason, I’m going to share with you a demonstration of Javascript closure in relation to event handlers, or event listeners.

I will also include pictures and gifs of the before and after scenarios, complete with source codes on Code Pen.

In fact, the example here is a simplified version of a frustrating Javascript closure problem that I encountered lately.

It took me a full day to figure out the answer, and another day to understand why it didn’t work initially.

I know that I’m neither the first, nor the last victim to this closure beast, so consider this article my open learning note, and let’s dive in.

The idea

div class="service-name">
<h2 class="service">Sport Photography</h2>
<figure class="featured-image">
  <img src="https://source.unsplash.com/400x200/?sport" alt="Sport Photography">
</figure>
</div>
<div class="service-name">
  <h2 class="service">Street Photography</h2>
  <figure class="featured-image">
  <img src="https://source.unsplash.com/400x200/?street" alt="Street Photography">
</figure>
</div>

I have a simple html document with two <div> tags. Each div provides information about a photography service.

Inside each <div> tag, there is a <h2> tag which contains the service name, sport photography or street photography.

Following each <h2> tag is a <figure> tag, which then contains an <img> tag to give readers an idea of what my photography services look like.

This looks fine, but I’d like to push the envelope. I want the images to disappear by default and appear only when someone mouses over the service name.

Below is what I want the web design to look like:

Final look

Then comes Javascript closure

At first, I thought this would be a piece of cake.

var service = document.getElementsByClassName('service');
var imgList = document.getElementsByClassName('featured-image');
for (var i = 0; i < imgList.length; i++) {
  imgList[i].style.display = "none";
   service[i].addEventListener("mouseover",function() {
        service[i].nextElementSibling.style.display = "block"; 
      
      });

  service[i].addEventListener("mouseout",function() {
        service[i].nextElementSibling.style.display = "none";
      });
}

What I did was to get all elements with .service class aka the H2 headers for Sport Photography and Street Photography.

Next, I got all elements with .featured-image class, or the figure container that stores the pictures.

Then I set the display style for the .featured-image elements as none, which can only be achieve with for loop.

Last, I set up two addEventListener methods for mouseover and mouseout.

Mouseovercontains a function that will change the display style of .featured-image class to block when someone hovers over the .service class.

When they hover off that class, however, the images will disappear, just like the beginning.

All this for loop and event listeners are nested inside the function addLink().

But look at what happens?

Javascript closure 2

The images are hidden by default.

The reason

The result may be different, but we are still plagued by the same Javascript closure.

We bind the variable i in the two addEventListener methods to the global i outside. There is always a mismatch in the value of the i inside and i outside.

The mouseover or mouseout event is only executed when someone hovers on or off the targeted element.

Meanwhile, the i outside increments until it is equal to 1, smaller than 2. That means functions for mouseover and mouseoutwill always return the current value of global i at the time you invoke it.

Therefore, the targeted element’s style, the .featured-image class in this case will always be set to none.

A refresher of Javascript closure

I don’t intend to write about all the nuts and bolts of closure in Javascript, but here’s the most understandable definition of closure that I’ve found:

A closure gives you access to an outer function’s scope from an inner function.

Eric Elliott

Generally, declaring functions in a loop is a bad idea, but for the sake of practice, we’ll try to find a way to get through this problem.

How to deal with Javascript closure

Use an IFFE

IFFE, or Immediately Invoke Function Expression is a solution to protect the scope of your function and the variables within it.

In our case, we want variable i inside the mouseover and mouseout functions to be copied from, but not bound to the global i.

In other words, global i changes, but inner i stays the same, because the for loop and the functions for event listeners are operating on two different scopes.

Here’s what an IFFE looks like:

(function(i) {
      return function() {
  service[i].nextElementSibling.style.display = "block"; 
      }
  }(i))

An IFFE is wrapped inside brackets, and that’s how you tell it apart from traditional functions.

It usually has no name, because once we’ve created it, we’ll call it only once, and have no intention of calling it again.

In order to call the IFFE, we add a pair of brackets at the end of the function, just before the semicolon. Inside the brackets is, of course, the parameters for our IFFE.

Our final codes will look like this:

var service = document.getElementsByClassName('service');
var imgList = document.getElementsByClassName('featured-image');

for (var i = 0; i < imgList.length; i++) {
  imgList[i].style.display = "none";
    service[i].addEventListener("mouseover",(function(i) {
      return function() {
  service[i].nextElementSibling.style.display = "block"; 
      }
  }(i)));
  service[i].addEventListener("mouseout",(function(i) {
      return function() {
  service[i].nextElementSibling.style.display = "none";
      } 
  }(i)));
}

The result:

Create Javascript closure with IFFE

If you are wondering why the images are different, that’s because I use the Unsplash source for embedding placeholder images, and I choose random options.

Use let statement

To be honest, I never fully understand the different between let and var statements until I encounter closure, a rites of passage for any Javascript learners.

The let statement lets you declare a block scope variable. Actually, this is a fancy way to refer to the action of making your local variable i unchained by the global i.

for (let i = 0; i < imgList.length; i++) {
  imgList[i].style.display = "none";
  service[i].addEventListener("mouseover",function() { 
    service[i].nextElementSibling.style.display = "block";
  }); 
  
  service[i].addEventListener("mouseleave", function() {
    service[i].nextElementSibling.style.display = "none";
  );
};

Tadah!

Create Javascript closure with let statement

Wrap-up

Javascript closure has always been a mythological concept to grasp, but I think it’s even more flabbergasting when we involve for loop and addEvenetListeners.

Do you have a more efficient way to to display image on hover? Or is there any juicy lesson about Javascript closure that you would like to share? Please share your insights in the comments.

Stay inspired. Subscribe to my infrequent newsletters.(You won't regret it).

Leave a Reply

Your email address will not be published. Required fields are marked *