In Defence of jQuery

There are countless articles and websites dedicated to telling you that you don’t need jQuery any more. But these articles rarely tell the whole story of one of the most useful libraries ever in web development.

jQuery has been around since the dawn of man, and the original versions were pressed into clay and then baked. Modern sensibilities tell us that jQuery’s utility has gone, that it’s no longer helpful. But often these articles handwave some genuine issues that show jQuery actually still provides significant value.

The biggest of these articles is an entire website at you might not need jquery. It’s important to be up front here. The site is well titled. You might not. You probably don’t. But you also might.

First of all, we need to be clear what we’re talking about and establish some understandings. For one, there is a big difference between using jQuery that’s already established in an existing application, and implementing it in a new project. While the latter is a harder sell, the former is much more obvious. There’s really no benefit pulling out working functionality just because some numpty on Reddit told you jQuery is Satan’s library.

There are three main reasons we used jQuery. And to some extent, all three still have some merit.

Selection Engine

Let’s not pretend jQuery’s selection engine isn’t a goddamn miracle on its own. For a start you can select some crazy arbitrary nonsense.

$(".registration-form input:not([type='password']):hidden");

Sure, maybe you don’t need to get all of the inputs of the registration field that are currently hidden (except the password fields). But maybe you do. Browsers have come a long way now in implementing document.querySelectorAll. This API lets vanilla JavaScript select anything the CSS engine can, which is great. But jQuery’s selector engine still has some functionality (as above, the :hidden, or jQuery’s siblings() function), that the querySelectorAll doesn’t work for. Either in selection or in traversal.

Even assuming a selector like this or an equivalent worked, and even in selectors that do work, the API comparison is pretty interesting.

// selection by id
$('#my-id') // jquery
document.getElementById('id') // js
// selection by class
$('.my-class') // jquery
document.getElementsByClassName('my-class') // js
// arbitrary selection
$('[data-whatever]') // jquery
document.querySelectorAll(‘#my-id .my-class’) // js

Three completely different APIs. In the case of jQuery they all return the same thing: a jQuery object with an array of elements. The vanilla JS calls return an element, a HTMLCollection and a NodeList, respectively.

Let’s just say we consolidate on the more modern and versatile approach and go for the querySelectorAll. That means we’ve decided on a NodeList. That gives us a consistent API across different selectors, and great, off we go. Now we need to use that to bind some components. I have an established a pattern in an old legacy application of creating ES6 modules and binding the element inside the constructor.

$('[data-contact-form]')
.each((index, element) => new ContactForm(element));

document.querySelectorAll('[data-contact-form]')
.forEach(element => new ContactForm(element))

It’s obvious how much terser the jQuery version is in its selector, if not the rest. But what’s not obvious is that the JS version actually won’t work. At all. As I said earlier, the querySelectorAll won’t return an array, but a NodeList. And a NodeList is not iterable. Yep, that’s right. Well, that’s partially right. It can be iterated, but you need to use the for(item of list) syntax to do it, and can’t do things like .map or .filter.

If you read enough docs you’ll find a few hacky ways to convert it:

Array.prototype.slice.call(myNodeList); // oldskool
let myArray = Array.from(myNodeList);   // new hotness

But even the better ES6 syntax is still a bit of a dick, some extra handling that you have to learn about the hard way.

DOM Manipulation

Let me say this very clearly: this is where jQuery fails. jQuery is good at direct DOM manipulation and that’s kind of its downfall. The key learning from every real JavaScript framework is creating a state object, and having that state reflect itself onto the DOM.

This is how we should be handling the DOM these days. What I’m talking about now is not to say that you should be manipulating the DOM directly. What I’m saying is that if you have to or choose to, jQuery is vastly easier.

Part of the problem is that “you don’t need jQuery anymore” articles include such trivial code that there’s no real distinction.

The jQuery way:

element.on('click', handler);

Vanilla JS option:

element.addEventListener('click', handler);

Pretty much the same thing, right? It is, except that the jQuery version will happily take a list of elements, and bind an event to all of them. The Vanilla version needs to loop over all of the… whatever they are (see above) and set a listener.

More specifically, the jQuery version allows you to do this:

$('.nav').on('click', '.nav-button', handler);

Note the difference here. This is a delegated event. That means it puts one watcher on the parent element, and triggers on any matching element inside that. This can also be done on elements that aren’t yet in the DOM, useful for elements created by Ajax calls, etc.

The same can be done in vanilla js, but it involves quite a lot more setup, and needs you to explicitly put a bunch of if(element.matches('.nav-button')) checks on one big ole event watcher function.

There are other things regularly omitted from jQuery comparisons.

$('#heading').css('color', 'red');
document.getElementById('heading').style.color = 'red';

I mean, don’t change inline styles and all, but these are basically the same, right? YouMightNotNeedJQuery.com seems to think so. But jQuery can act on entire collections, and its syntax doesn’t change to do so.

$('.heading').css('color', 'red');
for(const heading of document.getElementByClassName('heading') {
heading.style.color = 'red';
}

Here’s an example that I know for a fact isn’t at all contrived because I’ve literally implemented it in the last week. A fairly simple nav system, turning on and off panels underneath them. The button gets a state active and inactive swapped on click, while the section below gets turned on and off with a show and hide class. In production these are slightly more complex, with multiple classes, but for clarity that will do.

Here’s the code in current vanilla js. This may not look like an apples-to-apples comparison, because I made a lot of functions in standard JS, but in my defence I kind of had to in order to keep it sane.

const commandButtons = document.getElementById('buttons');
const commandSections = document.getElementById('sections');
const buttonActivate = button => {
button.classList.add('active');
button.classList.remove('inactive');
}
const buttonDeactivate = button => {
button.classList.remove('active');
button.classList.add('inactive');
}
const sectionShow = section => {
section.classList.add('show');
section.classList.remove('hide');
}
const sectionHide = section => {
section.classList.remove('show');
section.classList.add('hide');
}
const enableCommandSection = section => {
let sections = commandSections.getElementsByClassName('section');
  for (const sectionElement of sections) {
sectionHide(sectionElement);
}
let selectedSection = commandSections.querySelectorAll(`[data-section="${section}"]`)[0];
sectionShow(selectedSection);
}
commandButtons.addEventListener('click', ({target}) => {
if (target.matches('.button')) {
if (target.matches('.active')) {
return false;
}

let btns = commandButtons.getElementsByClassName('button');
    for (const btnElement of btns) {
if(btnElement !== target) {
buttonDeactivate(btnElement);
}
}
buttonActivate(target);
enableCommandSection(target.dataset.command);
}
});

I’m not trying to misrepresent standard JS here. As far as I can tell this is the clearest expression of this functionality. I wrote this as production code, so if you have genuine improvements (ie, not pointless code golfing or micro-optimisations) feel free to share.

By contrast, this is the jQuery equivalent.

$('#buttons').on('click', '.button', function(){
if (this.hasClass('active')) {
return false;
}
  this.addClass('active').removeClass('inactive')
.siblings().addClass('inactive').removeClass('active');
  $('.section', $('#sections'))
.removeClass('show').addClass('hide');
  $(`[data-section=${this.data('section')}]`, $('#sections'))
.removeClass('hide').addClass('show');
});

This should do exactly as above.

This isn’t an artificial example, this is something I had to do within the last week. I should also point out (before anyone else does) that I could have used .toggleClass in jQuery and made that even terser. However, I’ve seen too many interfaces get out of sync and turn off when they should turn on, so I prefer to be explicit in my adding and removing of classes.

The point here is that it’s easy to make arbitrary and simple DOM changes and then proclaim that jQuery isn’t necessary to make them. But when you start dealing with more complex DOM manipulation, the situation becomes much less clear-cut. jQuery’s optimisations in terms of selectors and its underlying API make for a lot less code, and a lot more comprehensible code than a lot of native APIs.

The You Might Not Need jQuery website actually makes the case for jQuery better than I could. Pretty much every example in there is vastly longer without jQuery. The .fadeIn one being a particularly funny example. They always have to specify which versions of what browsers it will work for, and often have a conditional or two to support different browsers.

Ajax and XHR

The final thing jQuery has long been used for is for its $.ajax functionality, which wraps up XHR, aka Ajax requests.

I’ve actually written a whole other article about this point, but IMO the native fetch API is a heap of shit, and if you need to polyfill it with a library anyway, what’s the advantage? You might as well install a library that doesn’t suck like Axios, and if you’re installing libraries… why not jQuery?

I’m not really advocating installing jQuery just to use it for Ajax, I’m just saying that this is actually something it does pretty well, and something that isn’t nearly as well replaced by native functionality as people seem to act.

Again YouMightNotNeedJquery.com has our backs, with long examples of complex code for native XHR requests.

var request = new XMLHttpRequest();
request.open('GET', '/my/url', true);

request.onload = function() {
if (request.status >= 200 && request.status < 400) {
// Success!
var data = JSON.parse(request.responseText);
} else {
// We reached our target server, but it returned an error

}
};

request.onerror = function() {
// There was a connection error of some sort
};

request.send();

vs

$.getJSON('/my/url', function(data) {

});

But that site also handwaves a lot of optimisations as well. For example, the comparison between “Requests” has nearly double the code for a native XHR call by comparison to a jQuery .ajax function. But even that isn’t very good jQuery.

$.ajax({
type: 'GET',
url: '/my/url',
success: function(resp) {

},
error: function() {

}
});

Why wouldn’t that just be this?

$.get('/my/url')
.done(() => {})
.fail(() => {})

In Conclusion

Let me be clear. I’m not saying you should use jQuery.

I’m saying that if you’re going to evaluate, criticise, or abandon jQuery you should do so with a full awareness of what you’re doing and why.

You should be aware that native APIs like FormData and Fetch can be a complete asshole to work with and often require feature checks and polyfills. You should be aware that there is no consistency between what is returned by native selector functions like getElementById, getElementsByClassName and querySelectorAll. You should expect a lot of explicit iteration for functionality on every item returned by selectors. You should expect to have to look up whether things work on any given browser. My use of element.classlist above eliminates any version of IE before 10, which I don’t care about, but maybe you do?

As a last point, I’m not pro-jQuery. The entire approach is wrong, and that’s something I’ve written about before. The code above would be so much simpler done in a framework like React or Ember.

handleNavItemClick(section) {
this.setState({activeNav: section});
}

But if we’re going to have a serious discussion about jQuery’s continued utility, we should be able to do it without the strawman arguments.

We should also talk about filesize. This is the obvious reason people bring up for removing jQuery. And by all means, if you can optimise, you should. The full release (not slim, which drops ajax support) of jQuery is a hefty 86kb. It’s possibly a bit less than that when gzipped, but a reasonable ball park. Ditching near 100k is always a plus. I recently deployed a prototype React project with trivial amounts of code and when fully built it was nearly 10 meg. I think there is some tuning of Webpack needed here because that was way high. But a similarly scoped Ember app with production flags on build compiled down to 1.5 meg, probably more of a realistic size. Given it’s not uncommon for web apps to be around 5 meg, is the 70k for Bootstrap really the biggest rock?

This is without mentioning that jQuery, as the single most popular dependency is almost certain to be cached from CDN by the browser if you go that route, meaning it’s actual file size is more like zero.

Again. You might not need jQuery. But if you’re aggressively removing or avoiding it as an optimisation, make sure it’s not a premature one.

Like what you read? Give Matt Burgess a round of applause.

From a quick cheer to a standing ovation, clap to show how much you enjoyed this story.