Himanshu SharmaSoftware engineer
Let’s talk

jQuery · Lesson 42 of 45

jQuery and modern JavaScript side by side

Compare common jQuery code with modern JavaScript (querySelectorAll, classList, addEventListener, fetch) and decide when each is the better choice.

13 minjQuery todayUpdated 23 October 2026

By the end of this lessonRead and translate between jQuery and plain JavaScript, so you can maintain jQuery sites and write new code without it when that is simpler.

Why compare?

jQuery was created when browsers disagreed about basic things. Today they agree, and many jQuery methods have a direct built-in equivalent. Knowing both lets you:

  • maintain jQuery sites confidently,
  • read modern tutorials and code that use plain JavaScript,
  • decide, project by project, whether jQuery is worth loading.

Each example below gives the same output both ways.

Selecting

html
<ul id="menu"><li class="item">Home</li><li class="item active">Shop</li><li class="item">Blog</li></ul>

<script src="https://code.jquery.com/jquery-4.0.0.min.js"></script>
<script>
  // jQuery
  console.log("jQuery:", $(".item").length, $(".item.active").text(), $("#menu").find("li").first().text());

  // Plain JavaScript
  const items = document.querySelectorAll(".item");
  console.log("plain: ", items.length, document.querySelector(".item.active").textContent, document.querySelector("#menu li").textContent);
</script>
result
jQuery: 3 Shop Home
plain:  3 Shop Home
jQuery Plain JavaScript
$("#id") document.getElementById("id") or querySelector("#id")
$(".item") document.querySelectorAll(".item")
$(el).find(".x") el.querySelectorAll(".x")
$(el).closest(".card") el.closest(".card")
$(el).parent() / .children() el.parentElement / el.children
$(el).next() / .prev() el.nextElementSibling / el.previousElementSibling

One important difference: when nothing matches, querySelector returns null, and calling a method on null throws an error. jQuery returns an empty object and quietly does nothing.

Changing every element

jQuery acts on every element in a selection. Plain JavaScript does not: you loop yourself.

html
<p class="note">One</p><p class="note">Two</p>

<script src="https://code.jquery.com/jquery-4.0.0.min.js"></script>
<script>
  // jQuery: implicit iteration
  $(".note").addClass("seen").attr("title", "Read");

  // Plain JavaScript: loop over the NodeList
  document.querySelectorAll(".note").forEach(p => {
    p.classList.add("checked");
    p.setAttribute("data-read", "yes");
  });

  console.log([...document.querySelectorAll(".note")].map(p => p.className + " / " + p.title + " / " + p.dataset.read).join(" | "));
</script>
result
note seen checked / Read / yes | note seen checked / Read / yes
jQuery Plain JavaScript (one element)
.addClass / .removeClass / .toggleClass / .hasClass el.classList.add / .remove / .toggle / .contains
.text() / .text(v) el.textContent
.html() / .html(v) el.innerHTML (same XSS warning)
.attr(n) / .attr(n, v) / .removeAttr(n) el.getAttribute(n) / setAttribute / removeAttribute
.prop("checked") / .val() el.checked / el.value
.data("userId") el.dataset.userId (always a string)
.css("color", "red") el.style.color = "red"
.css("color") (reading) getComputedStyle(el).color

Creating and inserting

html
<ul id="list"><li>First</li></ul>

<script src="https://code.jquery.com/jquery-4.0.0.min.js"></script>
<script>
  // jQuery
  $("<li>", { text: "Added by jQuery", "class": "new" }).appendTo("#list");

  // Plain JavaScript
  const li = document.createElement("li");
  li.textContent = "Added by plain JS";
  li.className = "new";
  document.querySelector("#list").append(li);

  // Both remove the same way
  document.querySelector("#list li").remove();
  console.log($("#list li").map((i, el) => el.textContent).get().join(" | "));
</script>
result
Added by jQuery | Added by plain JS

Modern elements have append, prepend, before, after, replaceWith and remove built in, with the same meanings as jQuery's.

Events

html
<ul id="list"><li>Apple</li><li>Banana</li></ul>

<script src="https://code.jquery.com/jquery-4.0.0.min.js"></script>
<script>
  // jQuery delegation
  $("#list").on("click", "li", function () {
    console.log("jQuery:", $(this).text());
  });

  // Plain JavaScript delegation
  document.querySelector("#list").addEventListener("click", event => {
    const li = event.target.closest("li");
    if (li) console.log("plain: ", li.textContent);
  });
</script>
result
jQuery: Banana
plain:  Banana

addEventListener has no selector argument, so delegation in plain JavaScript uses event.target.closest(). There are no namespaces either; to remove a handler you keep a reference to the function, or pass an AbortController signal and abort it.

Ajax versus fetch

html
<script src="https://code.jquery.com/jquery-4.0.0.min.js"></script>
<script>
  const url = "https://jsonplaceholder.typicode.com/users/1";

  $.getJSON(url).then(user => {
    console.log("jQuery:", user.name);
    return fetch(url);
  }).then(response => {
    if (!response.ok) throw new Error("HTTP " + response.status);
    return response.json();
  }).then(user => {
    console.log("fetch: ", user.name);
    return fetch("https://jsonplaceholder.typicode.com/users/99999");
  }).then(response => {
    console.log("fetch 404 still resolved: ok =", response.ok, "| status =", response.status);
  });
</script>
result
jQuery: Leanne Graham
fetch:  Leanne Graham
fetch 404 still resolved: ok = false | status = 404

The biggest difference: fetch() does not reject on HTTP errors. A 404 or 500 still resolves, so you must check response.ok. $.ajax() sends them to .fail(). fetch also has no built-in timeout (use AbortSignal.timeout(ms)), and you send JSON with JSON.stringify and a Content-Type header yourself, just as with $.ajax.

Effects

Plain JavaScript has no .fadeIn() or .slideDown(). The modern replacements are CSS transitions toggled with classList (see the CSS transitions lesson), or the Web Animations API:

javascript
element.animate([{ opacity: 0 }, { opacity: 1 }], { duration: 300, fill: "forwards" });

Document ready

javascript
// jQuery
$(function () { /* ... */ });

// Plain JavaScript: put scripts at the end of body, or use defer, or:
document.addEventListener("DOMContentLoaded", () => { /* ... */ });

So which should you use?

Keep jQuery when a site already uses it, especially with jQuery plugins or WordPress themes that depend on it. Rewriting working code costs time and risks new bugs; upgrade it to jQuery 4 instead (next lesson).

Consider plain JavaScript for new, small scripts. You save a download, and the code runs anywhere without dependencies.

Use a framework (React, Vue, Svelte) for large interactive applications, where keeping many parts of the page in step with data is the main problem. Do not mix a framework and jQuery on the same elements.

Whatever you choose, the ideas you learned in this course, selecting, traversing, events, delegation, safe text, Ajax and promises, are exactly the same in every tool.

Common mistakes

  • Calling methods on querySelector's null when nothing matches.
  • Expecting querySelectorAll results to have jQuery methods, or implicit iteration.
  • Forgetting response.ok with fetch().
  • Rewriting a working jQuery site just because jQuery is old.

Exercise

Translate this jQuery into plain JavaScript, keeping the same behaviour:

javascript
$("#toggle").on("click", function () {
  $("#panel").toggleClass("open");
  $(this).attr("aria-expanded", $("#panel").hasClass("open"));
});
Show one possible answer
html
<button id="toggle" aria-expanded="false">Menu</button>
<div id="panel">Panel</div>

<script>
  const toggle = document.querySelector("#toggle");
  const panel = document.querySelector("#panel");
  toggle.addEventListener("click", () => {
    const open = panel.classList.toggle("open");
    toggle.setAttribute("aria-expanded", open);
    console.log("aria-expanded:", toggle.getAttribute("aria-expanded"));
  });
</script>
result
aria-expanded: true

classList.toggle() returns true when it added the class, which saves the separate hasClass check.

Key points

  • document.querySelector and querySelectorAll select elements like $(), but return elements or a NodeList, not a jQuery object.
  • Plain JavaScript has no implicit iteration: loop over a NodeList with forEach to change every element.
  • classList, textContent, dataset, addEventListener, closest and fetch cover most everyday jQuery methods.
  • jQuery remains useful for existing code, quick scripts and its plugins; new projects often do not need it.

Check yourself

1. What does document.querySelectorAll(".item") return?
2. What is the plain JavaScript version of $(el).toggleClass("open")?
3. How does fetch() differ from $.ajax() for a 404 response?
4. When is keeping jQuery the sensible choice?