Selectors and traversal
What the jQuery function really returns, how to move around the document with traversal methods, and why chaining works at all.
The jQuery object
<script src="https://code.jquery.com/jquery-3.7.1.min.js"></script>
<script>
// runs after the HTML is parsed
$(function () {
$('.card').addClass('ready');
});
</script>$ is a function that takes a CSS selector and returns a jQuery object — a wrapper around an array of matched DOM elements, plus dozens of methods. jQuery and $ are the same function; jQuery.noConflict() releases the dollar sign if another library needs it.
| Expression | Matches |
|---|---|
$('#main') | The single element with that id (ids must be unique anyway) |
$('.card') | Every element carrying that class |
$('ul > li') | Direct children with native CSS combinators |
$('a[href^="https"]') | Attribute prefix match |
$(':input') | A jQuery-only extension — valid, but slow; prefer $('input, select, textarea') |
$(element) | Wraps an existing DOM node you already hold |
$(htmlString) | Parses new markup into detached elements |
// a context argument scopes the search and speeds it up
$('li', $list); // same as $list.find('li')
// plain DOM methods still work on the wrapped elements
$('#main')[0].scrollTop = 0;
$('#main').get(0); // explicit and readableTraversal and filtering
const $list = $('ul.todos');
$list.find('li'); // descendants matching li
$list.children('li'); // direct children only
$list.parent(); // immediate parent
$list.closest('.panel'); // nearest ancestor matching (or self)
$list.siblings();
$list.nextAll('.divider');
$list.eq(0); // wrap the first element in a new set
$list.filter('.done');
$list.not('.done');
$list.has('input:checked');
$list.is('.empty'); // returns a boolean, not a set
$list.index(); // position among its siblings| Method | Returns | Use it to |
|---|---|---|
find / children | new set (descendants) | Drill down from a container |
parent / closest | new set (ancestors) | Walk up to the component root |
filter / not / eq | new set (subset) | Narrow a set before acting |
is | boolean | Branch logic on a state check |
each | the same set | Run a side effect per element |
end | previous set | Undo one chaining step |
- Traversal methods never re-run the selector on the document — they walk the existing elements, which is why they are usually cheaper than a new query.
children()andfind()are not interchangeable: the first stops at one level, the second goes all the way down.closest()includes the element itself, so it is the right tool for delegated handlers.
Chaining and set behaviour
$('.row')
.filter('[data-active="1"]')
.addClass('highlight')
.find('.label')
.text('Active')
.end() // step back to the filtered rows
.css('opacity', 1);
// each() is for side effects; map() gives you plain values
const ids = $('.row').map(function (i, el) {
return el.dataset.id;
}).get(); // ["7", "9"] — a real array- Every method returns a jQuery object, which is what makes chaining legal.
each()returns the original set, not the callback results — usemap().get()to collect values.- Inside the callbacks,
thisis the raw DOM element: wrap it with$(this)before calling jQuery methods.
💡
A jQuery object is a set, never a single element — even when it is empty. Methods on an empty set are silent no-ops, which removes the null checks plain DOM code needs, but it also means typos fail quietly instead of throwing.
FAQ
Should I cache the result of a selector?
Yes, whenever you reuse it. Assigning
const $row = $('.row') once and reusing the variable avoids repeated document queries, and the set stays live only if you re-query — jQuery sets are snapshots, not live collections.Is <code>$()</code> faster than <code>querySelectorAll</code>?
Not anymore. Native CSS selectors in modern browsers are very fast and jQuery delegates to them internally; the value of jQuery is the method surface and browser consistency, not raw selection speed.
Related
Working with the DOM Events, effects and attributes
Last refreshed 2026-09-18.