Moving on from jQuery selectors


jQuery's selectors are probably one of the most used features of the library. They are easy to use and quite powerful. What they are not, is fast. So what is the vanilla javascript alternative? You're probably sick of typing out .getElementById() and .getElementsByClassName(), understandable. Now vanilla javascript has an answer with .querySelector() and .querySelectorAll(). You can use your CSS selectors in there just like you would with jQuery. So what does it look like?

jQuery

$("body");

Vanilla

document.querySelector("body");

So you might be thinking: "Okay, sweet! So I'll just replace the $ with document.querySelector in my code and everything will be faster!". If only it were that easy... Now to most people reading this it's probably already obvious why. It's the reason why there's 2 functions. .querySelector() and .querySelectorAll(). It's all about the return types. So let me show you what these functions return.

jQuery

// First let's select the body, there should be only one

$("body");
// This returns a jQuery object

// Now let's select the divs, there should be multiple

$("div");
// This also returns a jQuery object

Vanilla

// Let's start with selecting the body again

document.querySelector("body");
// This returns the body element

// Now let's throw the divs in that query

document.querySelector("div");
// This returns 1 div element, the first one

// We use the other function to get all elements

document.querySelectAll("div");
// This returns all the div elements in an array-like object

So there's our first problem. You need to know in advance if you're selecting one or more elements. But we could just write a quick utility function where it doesn't matter anymore, right? Something like this:

function customQuery(query) {
    let result = document.querySelectorAll(query);
    if (result.length < 2) {
        return result[0];
    } else {
        return result;
    }
}

// Now we can just call this function

customQuery("div");
Fixed? Not quite. This is really bad and fairly useless code. So what happens here? You just put in your query and it returns one or more elements, that's what you wanted, right? The problem is once again return types. Because what does this function return? The answer: it depends. Javascript allows you to this because of it's dynamic typing, but it's not a very good thing to do.
  • $(query); always returns a jQuery object.
  • document.querySelect(query); always returns an element.
  • document.querySelectAll(query); always returns an array-like object.
  • customQuery(query); sometimes returns an element, sometimens an array-like object.
Now, you could go writing your own jQuery-like functions, that can take in either those elements or array-like objects, but that still leaves you with problems. Even if you recommend people to use your own custom functions, they can still decide to use vanilla functions. They'll be able to use customQuery("div").innerHTML() sometimes, if it happens to return an element, but if it doesn't, things go wrong. Darn. How do we solve this? Should we make our customQuery() return objects, the same way jQuery does? That sounds complicated and counterproductive, might as well just use jQuery then. But then what?

At this point it's a bit down to opinion, but here is my suggestion:

  • Don't use document.querySelector().

    This might seem strange, but I think in most cases document.querySelectorAll() does what you want. It always returns an array-like object, even if there's only one element in there. Just make sure to treat it as an array too, using things like .forEach() for example.

Now we're left with only 1 more problem. The use of functions you have on jQuery objects. You can't just call jQuery's addClass() on the returned object. Now this is where I recommend writing your own custom functions, as you're likely not using every single jQuery function anyway. You won't be able to call these functions directly on your objects, however. I will write an example of a function that emulates the functionality of addClass(), that function will take in the object and the name of the class you want to add.

function addClassToElements(queryObject, className) {
    queryObject.forEach((element) => {
        element.classList.add(className);
    });
}

// Now we are going to add the class "custom" to every div using this function

var alldivs = document.querySelectorAll("div");
addClassToElements(alldivs, "custom");

Tips

I think at this point I've given you everything you need to get started replacing your jQuery selectors. I walked you through some mistakes I made the first time, so you don't have to. There are definitely some more things you can do though, so here are some tips.

  • You can not only call .querySelectorAll() on document, but also other elements.
  • Learn more about these and other Javascript functions on MDN
  • Learn how to write some jQuery functions in vanilla Javascript on youmightnotneedjquery.com

I hope this article could help you start writing some great new Javascript. Feedback is always appreciated.