Stphane
Stphane

Reputation: 3456

Difference between two ways of using a closure

The following exemples use the closure mechanism inside a for loop

I do know the purpose of closures but I'm not able to tell what is the best choice between the following pieces of code and in particular why one would be better than the other ?

// First case: Closure wraps the ajax call
$('#container').on('click', 'a.log', function (e) {
  _t = this;
  for (var i = 0; i < 5; i++) {
    (function (j) {
      $.ajax({
        url: "/logger",
        context: _t
      }).done(function () {
        $(this).addClass("done" + j);
      });
    })(i);
  };
});

// -------------------------------------------------------

// Second case :closure wraps the ajax callback function
$('#container').on('click', 'a.log', function (e) {
  for (var i = 0; i < 5; i++) {
    $.ajax({
      url: "/logger",
      context: this
    }).done(
      (function (j) {
        return function () {
          $(this).addClass("done" + j);
        };
      })(i)
    );
  };
});

I wish someone could explain me this precisly.

Thank you for your attention & time spent.

Upvotes: 0

Views: 50

Answers (2)

Bergi
Bergi

Reputation: 664423

Approach #1 focuses on the fact that the IEFE is used to preserve the loop iteration variable. You might even write it on one line - the whole body of the loop goes into the function:

for (var i=0; i<5; i++) (function(j) {
    …
})(i);

Approach #2 focuses more on the fact that the IEFE is used for providing j in the scope of the callback closure - you could call it (function makeCallback(j){ … })(i);.

In general, use what is better readable to you. You might always want to go with #1 as it makes creating other closures easier.


In this specific case, #1 led to a mistake: The this value you're passing as the context argument to the ajax function is not the one you expected from the event handler, but the one of the IEFE - undefined (or window in sloppy mode). You either will want to go with #2, or pass it explicitly:

for (var i=0; i<5; i++) (function(j) {
    …context:this…
}).call(this, i);
// or
for (var i=0; i<5; i++) (function(el, j) {
    …context:el…
})(this, i);

Upvotes: 1

Strille
Strille

Reputation: 5781

You could also consider moving out the immediately-invoked function expression to a separate function:

$('#container').on('click', 'a.log', function (e) {
  for (var i = 0; i < 5; i++) {
      doLogging(i, this);
  };
});

function doLogging(i, clickedElement) {
   $.ajax({
      url: "/logger",
      context: clickedElement
   }).done(function () {
      $(clickedElement).addClass("done" + i);
   });
}

I think it's a lot easier to understand what's going on, and you avoid having to introduce a new variable name 'j'.

You don't have to send in the clicked element, you could call doLogging using apply():

doLogging.apply(this, [i]);

In which case this inside doLogging() will refer to the clicked element.

Upvotes: 0

Related Questions