-1

By Burke Holland

When I first started using jQuery, I was so excited. I was using vanilla JS and really struggling with understanding when elements on a page were ready and how I could access them. When I learned about jQuery, I think I did what many people do. With tears of joy running down my face, I opened up a document ready function and then just vomited up massive amounts of jQuery.

Some of the worst jQuery code ever written was written by me – I can assure you of that.

Since then, I’ve spent a few years at the Bayside of jQuery. I’ve had my heart broken a few times and learned a some things along the way. In doing so, I’d like to share 5 things in jQuery that I think you should think twice about doing.

1. Stop Using Document Ready

Back in the day of heavy server frameworks, knowing that your page was fully constructed before you tried to mutate it was a big deal. This was especially true if you were using partial views of some sort that were injected into the layout/master page by the server at runtime.

Nowadays, it’s considered best practice to include your scripts at the bottom of the page. The HTML5 async attribute notwithstanding, scripts are loaded and executed synchronously by the browser. That means that if you have a large script in the head of your page, it will delay loading of the DOM and make your page seem slower than it actually is. Loading all scripts last will at least make your application seem to load faster. It also gives you a chance to use a spinner or loading overlay if you are completely dependent on JavaScript for your UI.

If you are adhering to the “scripts at the bottom” best practice, then you have no need for jQuery’s document ready function as the HTML is already loaded by the time the script is run.

<p id="zack">This element is on the page <strong>BEFORE</strong> all the scripts. No document ready needed.</p> <script src="https://ajax.googleapis.com/ajax/libs/jquery/1.9.1/jquery.min.js"></script> <script type="text/javascript" charset="utf-8"> // if you include your scripts at the very bottom, you don't need document ready (function($) { $("#zack").css("color", "green"); $("#slator").css("color", "red"); }(jQuery)); </script> <p id="slater">This element comes after the scripts and won't be available.</p>

2. Stop Using The Wrong Iterator

I’ve got no hard evidence to back this up, but I’d be willing to guess that the each function is the most used of the jQuery utility methods. I base this on how often I am tempted to use it and how often I see it in other people’s code. It’s incredibly convenient for looping over DOM elements and JavaScript collections. It’s also terse. There is nothing wrong with it, except that its not the only iteration function the jQuery provides. People use each like it’s Zack Morris, at which point every looping problem starts looking like Kelly Kapowski.

MAP

If you have an array of items and you want to loop through it and filter out some of the items, you might be tempted to use the each method like so:

(function($) { var allStarCast = [ { firstName: "Zack", lastName: "Morris" }, { firstName: "Kelly", lastName: "Kapowski" }, { firstName: "Lisa", lastName: "Turtle" }, { firstName: "Screech", lastName: "Powers" }, { firstName: "A.C.", lastName: "Slater" }, { firstName: "Jessie", lastName: "Spano" }, { firstName: "Richard", lastName: "Belding" } ] // iterate through the cast and find zack and kelly var worldsCutestCouple = []; $.each(allStarCast, function(idx, actor) { if (actor.firstName === "Zack" || actor.firstName === "Kelly") { worldsCutestCouple.push(actor); } }); console.log(worldsCutestCouple); }(jQuery));

Try It

That works, but jQuery actually provides a method specifically for this scenario and it’s called map. What it does is takes any items that get returned and adds them to a new array. The looping code then looks like this…

(function($) { var allStarCast = [ { firstName: "Zack", lastName: "Morris" }, { firstName: "Kelly", lastName: "Kapowski" }, { firstName: "Lisa", lastName: "Turtle" }, { firstName: "Screech", lastName: "Powers" }, { firstName: "A.C.", lastName: "Slater" }, { firstName: "Jessie", lastName: "Spano" }, { firstName: "Richard", lastName: "Belding" } ] // iterate through the cast and find zack and kelly var worldsCutestCouple = $.map(allStarCast, function(actor, idx) { if (actor.firstName === "Zack" || actor.firstName === "Kelly") { return actor; } }); console.log(worldsCutestCouple); }(jQuery));

Try It

Did you notice that the arguments for the .map() callback and the .each() callback are reversed? Watch out for that because it can bite you when you start using map.

The map function allows you to modify the items before they get slapped into the new array. I’m not currently doing that, so technically I’m still not using the “right” iterator. I should really be using grep.

GREP

If you spend most of your days on Windows, then this term might be foreign to you. The term is typically used for a Unix command line utility for searching files that contain text matching a given regular expression.

In jQuery, grep is a function whose purpose is to reduce a collection by removing elements that don’t pass the test in the callback. The provided callback returns a boolean value. Return true if you want the item to remain, and false(y) if you don’t. It will not affect the original array. You cannot modify the items as you reduce them.

(function($) { var allStarCast = [ { firstName: "Zack", lastName: "Morris" }, { firstName: "Kelly", lastName: "Kapowski" }, { firstName: "Lisa", lastName: "Turtle" }, { firstName: "Screech", lastName: "Powers" }, { firstName: "A.C.", lastName: "Slater" }, { firstName: "Jessie", lastName: "Spano" }, { firstName: "Richard", lastName: "Belding" } ] // iterate through the cast and find zack and kelly var worldsCutestCouple = $.grep(allStarCast, function(actor) { return (actor.firstName === "Zack" || actor.firstName === "Kelly"); }); console.log(worldsCutestCouple); }(jQuery));

Try It

3. Stop Using “this”

This isn’t a jQuery issue, but jQuery can definitely exacerbate the problem. The context of “this” is always changing. jQuery sometimes changes the context to something that you might not be expecting. In the each callback, this is the current item or element in the collection. In the map function, it’s the window object. You can see how you could get yourself confused pretty quickly.

Have a look at the following example and then we’ll talk about why it blows up.

(function($) { var sbtb = { log: function(message) { $("#log").append("").html(message); }, cast: [ { firstName: "Zack", lastName: "Morris", soExcited: false }, { firstName: "Kelly", lastName: "Kapowski", soExcited: true }, { firstName: "Lisa", lastName: "Turtle", soExcited: true }, { firstName: "Screech", lastName: "Powers", soExcited: false }, { firstName: "A.C.", lastName: "Slater", soExcited: false }, { firstName: "Jessie", lastName: "Spano", soExcited: true }, { firstName: "Richard", lastName: "Belding", soExcited: false } ], soExcited: function() { // use "this" to get a reference to the cast on this object $.each(this.cast, function(idx, actor) { // call the log function this.log(actor.firstName + " " + actor.lastName); // BOOM! Splosions. // "this" is now a cheesy actor, not the sbtb object anymore }); } }; sbtb.soExcited(); }(jQuery));

I wanted to log out everyone who was “so excited”. I’m in an object so I can get a reference to the object with this. Then I loop through the object and look for the “isExcited” flag. However, as soon as I enter the loop callback, the context of this has changed and I can’t access the object anymore.

Since jQuery is changing the scope for you in these loops, it’s a good idea to store the reference to this somewhere so that you know it’s not going to change on you.

(function($) { var sbtb = { log: function(message) { $("#log").append(" ").append(message); }, cast: [ { firstName: "Zack", lastName: "Morris", isExcited: false }, { firstName: "Kelly", lastName: "Kapowski", isExcited: true }, { firstName: "Lisa", lastName: "Turtle", isExcited: true }, { firstName: "Screech", lastName: "Powers", isExcited: false }, { firstName: "A.C.", lastName: "Slater", isExcited: false }, { firstName: "Jessie", lastName: "Spano", isExcited: true }, { firstName: "Richard", lastName: "Belding", isExcited: false } ], soExcited: function() { // store this in that so we don't get confused later on when // our the context of "this" changes out from underneath us var that = this; // use "that" to get a reference to the cast on this object $.each(that.cast, function(idx, actor) { // call the log function if (actor.isExcited) { that.log(actor.firstName + " " + actor.lastName); // the value of "that" doesn't change - it's still the object } }); } }; sbtb.soExcited(); }(jQuery));

4. Stop Using ALL THE jQUERIES

jQuery has steadily kept increasing in size. This is only natural as new functionality is added. Even though the size has steadily been reduced since 1.8.3, It’s led to some decry from the community claiming it “unsuitable” for mobile development due to it’s sheer mass.

However, jQuery is not an all or nothing library anymore. jQuery now supports custom builds. I know it’s really tempting to use the jQuery CDN and just rock on, but it is important to think about all the code that you are asking your users to download that they might not even need. That’s no big deal on a desktop, but bits get precious on mobile devices, and there is no point in sending down a bunch of code to support legacy browsers if your app is a mobile one.

You have two choices here. You can head over to the GitHub site and create a custom build with Git. It’s actually really easy and I had no issues getting it to work. But, if pounding out node commands is not your thing, John Resig tweeted about a web UI for custom builds the other day.

5. Stop Using jQuery…

…when you don’t need to.

I got to a point in my development career where the first thing that I did with any project was add jQuery, even if I was just creating very simple projects and samples. I did this mainly so that I could just use the DOM selection utilities. Back in the day of older browsers, this was easier to justify, but modern browsers have this whole DOM selection thing already nailed down for you.

DOCUMENT.QUERYSELECTOR

If you are using at least IE version 8 and above, you can just map the $ to document.querySelector, which will return you the first matched element from the selector. You can pass any CSS selector to the function.

Note that IE 8 only supports CSS 2.1 selectors for querySelector.