Whats Wrong With Your JavaScript Part 2

In this article, we will be exploring more of the common mistakes developers are making in their code. If you haven't already, you should check out part one. This time, however, I will be concentrating on jQuery code. Yes, people still use jQuery. It's extremely common in large corporations.

So let's get right back to it....

All your code in document.ready

I once had to re-write some code written by another developer that was about 2000 lines long. The worst part about the code? Every single line of it was in the document.ready event! I don't understand the need to do this. Yes, we have been trained to wait until this event before we try to access the DOM. And, mostly, this is fine. But that doesn't mean you have to wait for that event to set up your code! So, here is a simplified example:

$(document).ready(function(){

  $('#myBtn').on('click', function() {
    // literally 100 lines of code
  });

  $('#myOtherBtn').on('click', function() {
    // another 100 lines of code
  });

  $('#myLastBtn').on('click', function() {
    // you guessed it: 100+ lines of code
  });

  // etc., etc.

});

This is such a bad idea. First, good luck following your stack traces while you are debugging. Why? Because you have three anonymous functions and that is how they show up in the stack trace. Really the only thing that needs to be in document.ready is adding the event itself. All the actual code, could ( and should ) be written as stand-alone functions.

(function( $, window, document, undefined ) {
  // Start with our IIFE!!
  'use strict';

  function handleMyBtn() {
    // your 100 lines of code
  }

  function handleMyOtherBtn() {
    // your 100 lines of code
  }

  function handleMyLastBtn() {
    // your 100 lines of code
  }

  $(function() {   // shorthand for $(document).ready(function(){
    $('#myBtn').on('click', handleMyBtn);
    $('#myOtherBtn').on('click', handleMyOtherBtn);
    $('#myLastBtn').on('click', handleMyLastBtn);
  });

})( jQuery, window, document );

So, how is this better? Well, for one, our stack traces will now display our function names ( handleMyBtn, etc) which makes debugging a little easier. Also are functions are defined and parsed immediately not after the document ready event. Our ready function itself is a lot shorter and easier to read.

Using Depracated Methods

Another thing I see a lot is defining events using the "old" style:

$('.whatever').click(function(){ 
  //code here
});
$('.whatever').mouseover(function(){ 
  //code here
});
$('.whatever').mouseout(function(){ 
  //code here
});
$('.whatever').dblclick(function(){ 
  //code here
});

This way of writing event code in jQuery has been deprecated for a while. The best way to do this is with the on method. It works the same way as the above while also providing support for delegated selectors.

$('.whatever').on( 'click dblclick mouseover mouseout', myFunc );
function myFunc() {
  //one set of code here!
}

Namespace Your Events too!

With the above code, it would be even better to add your own namespace to the code. You do this by adding .[namespace] to the end of the event:

$('.whatever').on('click.gs dblclick.gs mouseover.gs mouseout.gs', myFunc);

Why would you want to do this? Well, let's say you have created your own click event on an element that already has a click event assigned to it. Now you want to remove your click event. You can't do this:

$('.whatever').off('click');

This will also remove any other code assigned to this event! But, if you namespaced your event, it's easy:

$('.whatever').off('click.gs');

Now only my click event will be removed!

Caching selections

The next thing we will talk about is caching your selectors. Because walking the DOM can be one of the slowest parts of jQuery, you want to do this as little as possible. Unfortunately, I see a lot of similar to this:

function whatevs() {
  $('.myclass').addClass('anotherclass');
  $('.myclass').on('click', function(){
    //some awesome code here
  }); 
  $('.btn').on('click', function( event ) {
    $('.myclass').removeClass('anotherclass');
  });
}

Whenever our function whatevs gets called, we dive into the DOM to find all instances of .myclass and add another class to them. Next, we dive into the DOM to find all instances of .myclass and add a click event. And lastly, whenever an element with a class of .btn (yes, we dive here too) is clicked dive into the DOM to find all instances of .myclass and remove another class. Hopefully, when you see it written out like that, you understand the problem.

So why do all that extra work? Let your code by lazy and only do that once. We could re-write the above code like this:

(function($) {  // simplified IIFE
  var $myclass = $('.myclass');
  var $btn = $('.btn');  // might as well cache this one as well.

  function whatevs() {
    $myclass.addClass('anotherclass');
    $myclass.on('click', function(){
      //some awesome code here
    }); 
    $btn.on('click', function( event ) {
      $myclass.removeClass('anotherclass');
    });
  }
})(jQuery);

You could simplify the first part further if you wanted by using chaining:

$myclass.addClass('anotherclass').on('click', function(){
  //some awesome code here
}); 

That's a personal style preference so I leave that up to you.

Throttle/Debounce Your Events

If you are going to write code against events like resize or scroll, you should really consider throttling or debouncing your code. What does that mean? Both of these are ways to prevent your code to responding to every single event call.

Let's start with an example. When you scroll a web page, the scroll event literally fires thousands of times. You don't really want your code to fire for every single instance. This can create a bottleneck. If your code takes three milliseconds to execute, and the scroll event fired one thousand times a second, then your code takes three seconds to execute. Your browser will start to be sluggish and this is not something you want to happen.

Throttling your code means to make it execute for every x number of events. So in the above example, say you set the throttle level to be 100. Using the example above your code would only be executed ten times which only takes thirty milliseconds. Much, much faster.

Debouncing your code means to let it execute every x amount of time. So, you could debounce at 100 milliseconds. Again, your code would only be executed ten times taking thirty milliseconds.

So which one should you use? It depends on your situation. Just remember debounce is based on the amount of time that has past and throttle is based on the number of times the event has fired.

For more information about this, and for a jQuery plugin, I highly recommend Ben Alman's article. It's an older article but the information it contains is still relevant today.

DRY-ing your code

I know this isn't jQuery specific, but I think it is definitely worth mentioning. If you are unfamiliar with the term it means - Don't Repeat Yourself. If you find yourself writing the same code (or very similar) code over and over, then you need to stop what you are doing and pull this code out and separate it into its own function. Here is a simple example:

$('#menuitem1').on('click', function() {
  var $menu = $('#mainmenu'),
      $this = $(this);
  $menu.find('.selected').removeClass('selected');
  $this.addClass('selected');
  $this.load($this.data('page'));
});
$('#menuitem2').on('click', function() {
  var $menu = $('#mainmenu'),
      $this = $(this);
  $menu.find('.selected').removeClass('selected');
  $this.addClass('selected');
  $this.load($this.data('page'));
});
$('#menuitem3').on('click', function() {
  var $menu = $('#mainmenu'),
      $this = $(this);
  $menu.find('.selected').removeClass('selected');
  $this.addClass('selected');
  $this.load($this.data('page'));
});

As you can see, each function executes the same set of code. We can pull that code out into its own function and just call it:

$('#mainmenu1').on('click', clickItem);
$('#mainmenu2').on('click', clickItem);
$('#mainmenu3').on('click', clickItem);

// or if you want a one-liner
// $('#mainmenu1,#mainmenu2,#mainmenu3').on('click', clickItem);

function clickItem(event) {
  var $menu = $('#mainmenu'),
      $el = $(event.target);
  $menu.find('.selected').removeClass('selected');
  $el.addClass('selected');
  $el.load($this.data('page'));
}

Another common example I run into is something like this:

if ( mynumber >= 50 ) {
 $el.addClass('high');
} else {
 $el.removeClass('high');
}

That's a lot of extraneous code. Did you know that jQuery's toggleClass method accepts a function as a second parameter? If that function returns true then it will add a class. If is is false, then it will remove a class. So the above code could be as simple as:

$el.toggleClass( 'high', ( mynumber > 50 ) );

Isn't that better? Short and succinct.

Conclusion

That's about it for part two. Hopefully by now, you are writing much cleaner JavaScript and jQuery code. I am not sure at this point if there will be a part three; I guess it depends on my next few code reviews.

'Til next time,
-G