Replace jQuery's this

Look out for

jQuery's this in .each() and event functions.

Resolve by

Using .each() element argument and event.currentTarget

// 'this' used twice for two different elements
$('.button-row').each( function() {
  // 'this' 1
  var $buttonRow = $( this );
  var $activeButton = $buttonRow.find('.button.is-active');

  $buttonRow.on( 'click', '.button', function() {
    $activeButton.removeClass('is-active');
    // 'this' 2
    $activeButton = $( this );
    $activeButton.addClass('is-active');
  });
});

Edit this demo on CodePen

// replacing 'this'
$('.button-row').each( function( i, buttonRow ) {
  // .each() element argument
  var $buttonRow = $( buttonRow );
  var $activeButton = $buttonRow.find('.button.is-active');

  $buttonRow.on( 'click', '.button', function( event ) {
    $activeButton.removeClass('is-active');
    // event.currentTarget
    $activeButton = $( event.currentTarget );
    $activeButton.addClass('is-active');
  });
});

Edit this demo on CodePen

Demo

Edit demo Button rows - no jQuery this on CodePen

Lesson

In the previous lesson on functions, we used this within both the .each() and event callback functions. In these contexts, jQuery provides this as a convenient keyword for individual elements being acted upon. As you continue to write jQuery, you grow so accustomed to using this, that you might not realize how weird it is.

Within the initial example code, this represents two different sets of elements. Because the initial example uses jQuery object variables, we can better recognize these elements as $buttonRow and $activeButton. Writing the example without variables, we can see how confusing this appears.

$('.button-row').each( function() {
  var $activeButton = $( this ).find('.button.is-active');

  $( this ).on( 'click', '.button', function() {
    $activeButton.removeClass('is-active');
    $activeButton = $( this );
    $( this ).addClass('is-active');
  });
});

Reading this code, it's hard to decipher what $( this ) signifies. It seems like every use of this represents the same thing. But we know that not to be true.

What is this?

this is both a bad part and good part of JavaScript. It is a powerful keyword, but easily confused.

this is meant to be self-referential. this is the way for objects to get, set, and use their own properties and methods. Ideally the this keyword would be self, which better represents its typical usage.

The ideal case for this is within classes. (By classes, I mean a custom type of object created by a developer, not class used in HTML or CSS.)

function Vector( x, y ) {
  this.x = x;
  this.y = y;
}

Vector.prototype.add = function( vec ) {
  this.x += vec.x;
  this.y += vec.y;
};

Vector.prototype.log = function() {
  console.log( this.x, this.y );
};

In the above code for a Vector class, its x and y properties are accessed across methods via this. You don't need to understand the above Vector code. Just know that the above code is an appropriate use of this.

The problem with this is that is has other uses. this has a different meaning depending on its surrounding code. In order to understand what this is, you need to look at its context.

(In the same manner, the mercurial of nature of this makes it dynamic. By enabling object to reference themselves, you can create functions and methods that are independent of the object's code. But that's a lesson for another day.)

Consider these four uses of this:

// global object
console.log( this );
// => window

// object method
var obj = {
  log: function() {
    console.log( this );
    // => {Object}
  }
};

// jQuery event listener
$('.button-row').on( 'click', 'button', function() {
  console.log( this );
  // => <button> element
});

// jQuery plugin
$.fn.log = function() {
  console.log( this );
  // => {jQuery} object
};

Each use has a different context. And there are other contexts that affect this. For a brief overview of this, see This in JavaScript by Zell Liew. For an in-depth analysis, read You Don't Know JS: this & Object Prototypes by Kyle Simpson.

Of these four uses, the jQuery event listener is the least similar. In that context, this doesn't reference itself, the $('.button-row') jQuery object. Rather, it references a separate element. This behavior in jQuery was taken from vanilla JavaScript's use of this in vanilla event listeners.

var buttonRow = document.querySelector('.button-row');
buttonRow.addEventListener( 'click', function() {
  console.log( this );
  // => <div class="button-row"> element
});

This is all to say that jQuery's use of this in event listeners and .each() is not improper, but it is quirky. It doesn't have the similar meaning of self, but a special meaning that's particular to one small facet of browser JavaScript.

We can alleviate ourselves from the quirkiness of jQuery's this by using other jQuery features.

.each() element argument

.each() provides two arguments for its function, an index integer, and the current iteration's element. That element argument is the same thing as this.

// using this
$('.button-row').each( function() {
  var $buttonRow = $( this );
});

// using each element argument
$('.button-row').each( function( i, buttonRow ) {
  var $buttonRow = $( buttonRow );
});

These two blocks of code work exactly the same. But using the .each() element argument has several benefits.

The buttonRow argument clearly signifies its purpose. It represents an individual element selected within the $('.button-row') jQuery object. You can follow its relationship from the '.button-row' selector string, to being used an element to create the individual $buttonRow jQuery object.

As an argument, buttonRow can be picked-up by linters, thus making it easier to catch typos or missing arguments. this, being a keyword, is always available in any code block. So linters cannot catch these kind of problems with using this.

The big benefit is that we can be explicit in our code. Using this is like saying "And you know what this is." Its meaning is implicit. buttonRow is an argument, so you can see where it comes from.

event.currentTarget

Within event listeners, you can replace this with event.currentTarget.

// using this
$buttonRow.on( 'click', '.button', function() {
  $activeButton = $( this );
});

// using event.currentTarget
$buttonRow.on( 'click', '.button', function( event ) {
  $activeButton = $( event.currentTarget );
});

event is the function's first argument. It contains many properties and methods useful event.preventDefault() for preventing default browser behavior, or event.pageX for mouse position. It also contains the current element selected for the event, event.currentTarget, the same element as this.

$buttonRow.on( 'click', function( event ) {
  console.log( event.currentTarget === this );
  // => true
});

event.currentTarget is different from event.target. event.currentTarget is the element specified in the jQuery object or filter argument. event.target is the element that initiated the event.

// clicking a <ul> with <li> items
$('ul').on( 'click', function( event ) {
  console.log( event.currentTarget );
  // => <ul>
  console.log( event.target );
  // => <li>
});

With event.currentTarget, our resolved code makes no use of this and instead relies on arguments for its elements.

$('.button-row').each( function( i, buttonRow ) {
  var $buttonRow = $( buttonRow );
  var $activeButton = $buttonRow.find('.button.is-active');

  $buttonRow.on( 'click', '.button', function( event ) {
    $activeButton.removeClass('is-active');
    $activeButton = $( event.currentTarget );
    $activeButton.addClass('is-active');
  });
});

Wrap up

this in .each() and event listeners seems convenient, but leads to quirky code. We can side-step issues with jQuery's this by replacing it with arguments.