The $ Function & Chaining

jQuery · Lesson 4 of 24 6 min read Updated

Every jQuery program is built from one function, $, and the jQuery object it returns. Once you understand what that object is, why one method call can change fifty elements, and why some calls can be chained while others cannot, the rest of jQuery is just a list of methods.

This lesson also shows the plain JavaScript equivalents, because the biggest difference between the two styles is right here: jQuery works on sets of elements, while the DOM works on one element at a time.

What you can pass to $()

$ (or its full name jQuery) does different jobs depending on what you give it:

JavaScript
$("li.done");                 // 1. a CSS selector: find matching elements
$("<li>Buy milk</li>");       // 2. an HTML string: create new elements (not yet on the page)
$(document.body);             // 3. a DOM element (or an array of them): wrap it
$(function () { /* ... */ }); // 4. a function: run it when the DOM is ready

In all of the first three cases you get back a jQuery object. In the plain DOM, these are different functions: document.querySelectorAll() for selecting, document.createElement() for creating, and you use elements directly without wrapping.

The jQuery object is a set

A jQuery object is an array-like list of DOM elements: it has a length and numbered positions, plus all the jQuery methods. It is never null, even when nothing matched.

JavaScript
const $items = $("#list li");
$items.length;        // how many elements matched (0 if none)
$items[0];            // the first DOM element itself (a plain <li>)
$items.get(0);        // the same thing
$items.eq(0);         // the first element, still wrapped in a jQuery object
$($items[0]);         // wrap a DOM element to use jQuery methods on it again

Many developers start variable names that hold jQuery objects with $, like $items, so they can tell at a glance that it is a jQuery set and not a DOM element. It is only a naming habit, not a rule.

Implicit iteration

When you call a setter (a method that changes something) on a set, jQuery applies it to every element in the set. You do not write a loop. This is called implicit iteration.

HTMLTry it yourself
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="utf-8">
  <title>Implicit iteration</title>
  <style>.sale { color: #b3261e; font-weight: bold; }</style>
</head>
<body>
  <h2>Farhan's Stationery, Pune</h2>
  <ul>
    <li class="price">Notebook: ₹60</li>
    <li class="price">Geometry box: ₹185</li>
    <li class="price">Fountain pen: ₹240</li>
  </ul>
  <button id="jq">Mark sale (jQuery)</button>
  <button id="js">Remove sale (plain JS)</button>

  <script src="https://cdn.jsdelivr.net/npm/jquery@4.0.0/dist/jquery.min.js"></script>
  <script>
    // jQuery: one call changes all three items
    $("#jq").on("click", function () {
      $(".price").addClass("sale");
    });

    // Plain JavaScript: you loop yourself
    document.querySelector("#js").addEventListener("click", () => {
      document.querySelectorAll(".price").forEach((li) => li.classList.remove("sale"));
    });
  </script>
</body>
</html>

querySelectorAll returns a NodeList, which has forEach but no classList, so you must loop. That loop is the main reason plain DOM code is a little longer.

Getters and setters

Most jQuery methods do two jobs. Call them with no value and they get (read); call them with a value and they set (write):

CallKindReturns
$("h1").text()gettera string
$("h1").text("Hi")setterthe same jQuery object
$("input").val()getterthe value of the first input
$("input").val("")setterthe same jQuery object, every input emptied

Two rules follow from this table:

  • Getters usually read only the first element. .val(), .attr() and .css() look at the first match. (.text() is the exception: it joins the text of all matched elements.)
  • Setters return the jQuery object, so you can call another method on the result. This is chaining.

Chaining

Because setters return the same set, you can write several steps in one statement. Put each step on its own line for readability:

HTMLTry it yourself
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="utf-8">
  <title>Chaining</title>
  <style>
    .card { border: 1px solid #ccc; padding: 0.75rem; border-radius: 8px; max-width: 18rem; }
    .ready { border-color: #2e7d32; background: #edf7ee; }
  </style>
</head>
<body>
  <div class="card" id="order">Order #1042: packing</div>
  <p id="info"></p>
  <button id="ship">Mark as shipped</button>

  <script src="https://cdn.jsdelivr.net/npm/jquery@4.0.0/dist/jquery.min.js"></script>
  <script>
    $("#ship").on("click", function () {
      const $card = $("#order")
        .text("Order #1042: shipped to Jaipur")
        .addClass("ready")
        .attr("data-status", "shipped");

      // A getter ends the chain because it returns a value, not the set
      const status = $card.attr("data-status");
      $("#info").text("Status saved as: " + status);
    });
  </script>
</body>
</html>

Plain DOM setters are mostly properties (el.textContent = "...") or methods that return undefined (classList.add()), so they cannot be chained. You write one statement per change instead, usually on a variable that holds the element.

Looping with .each()

When each element needs different treatment, use .each(). The callback receives the index and the DOM element, and this is also the current DOM element:

JavaScript
$(".price").each(function (index, element) {
  const rupees = Number($(element).text().split("₹")[1]);
  $(element).toggleClass("expensive", rupees > 200);
  console.log(index, rupees);
});

// Plain JavaScript
document.querySelectorAll(".price").forEach((element, index) => {
  const rupees = Number(element.textContent.split("₹")[1]);
  element.classList.toggle("expensive", rupees > 200);
  console.log(index, rupees);
});

Notice the argument order: jQuery's .each() gives (index, element), while forEach gives (element, index). Mixing them up is a classic bug when converting code.

If you use an arrow function with .each(), this will not be the element (arrow functions do not get their own this). Use the element parameter instead.

Common mistakes

  • Checking if ($("#x")) to see whether an element exists. A jQuery object is always truthy, even when empty. Check $("#x").length instead.
  • Calling jQuery methods on [0]. Indexing gives a plain DOM element. Use .eq() or wrap it with $().
  • Expecting a getter to read every element. $("input").val() returns only the first input's value.
  • Using this inside an arrow function with .each() or .on(). Use the element parameter or a normal function.

Key points

  • $() selects, creates, wraps or registers a ready handler, depending on its argument.
  • A jQuery object is an array-like set of DOM elements; .length tells you how many matched.
  • Setters apply to every element (implicit iteration) and return the set, so they can be chained.
  • Getters usually read only the first element and return a value, which ends the chain.
  • .each((index, element) => ...) loops when elements need different handling; plain forEach uses (element, index).

Exercise

Make a list of four train names with the class train. With jQuery, add the class checked to all of them in one call, then use .each() to append the position number to each name, for example "1. Shatabdi Express". Finally show the total count in a paragraph using .length.

Show solution
HTML
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="utf-8">
  <title>Trains</title>
  <style>.checked { color: #2e7d32; }</style>
</head>
<body>
  <ul>
    <li class="train">Shatabdi Express</li>
    <li class="train">Rajdhani Express</li>
    <li class="train">Vande Bharat Express</li>
    <li class="train">Duronto Express</li>
  </ul>
  <p id="count"></p>
  <script src="https://cdn.jsdelivr.net/npm/jquery@4.0.0/dist/jquery.min.js"></script>
  <script>
    const $trains = $(".train").addClass("checked");
    $trains.each(function (index, element) {
      $(element).text(index + 1 + ". " + $(element).text());
    });
    $("#count").text($trains.length + " trains listed");
  </script>
</body>
</html>

addClass changes every item at once, while .each() is needed because each item gets a different number.

Quick Quiz

1. What does $("#missing").length return when no element has that id?

2. What does $("p")[0] give you?

3. Why can you write $("h1").text("Hi").addClass("big")?

4. What is the argument order of the callback in jQuery's .each()?

Learn jQuery with a mentor

Free lessons get you started. Our Full Stack Web Development training adds real projects and code reviews from working developers.

  • Project-based learning
  • Mentor code reviews
  • Free counselling call