Filtering Selections
A selector gets you a starting set of elements. Often you then need a smaller part of it: only the products under ₹500, only the third row, every row except the header. jQuery has a family of filtering methods that take a set and return a smaller set, so you can keep chaining.
In this lesson you will learn those methods and their plain JavaScript equivalents, which mostly come from turning a NodeList into a real array and using array methods such as filter() and slice().
filter() and not()
.filter() keeps the elements that match. It accepts either a selector or a function. .not() is the opposite: it removes the elements that match.
$("li").filter(".in-stock"); // keep items with class in-stock
$("li").not(".in-stock"); // keep the others
// A function: return true to keep the element
$("li").filter(function (index, element) {
return Number($(element).attr("data-price")) < 500;
});As with .each(), the callback receives (index, element), and this is the element too.
In plain JavaScript, convert the NodeList to an array first. querySelectorAll() returns a NodeList, which has forEach() but no filter():
const items = [...document.querySelectorAll("li")]; // spread into a real array
const cheap = items.filter((li) => Number(li.dataset.price) < 500);
const notInStock = items.filter((li) => !li.matches(".in-stock"));element.matches(selector) returns true if the element would be selected by that CSS selector, which makes it the plain way to filter by selector.
Try both side by side on a small shop:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>Filtering products</title>
<style>
li { padding: 0.2rem 0.5rem; }
.pick { background: #dff0ff; }
</style>
</head>
<body>
<h2>Ananya's Book Corner, Lucknow</h2>
<ul id="books">
<li data-price="299" class="in-stock">Panchatantra Stories</li>
<li data-price="650">Learn Python the Simple Way</li>
<li data-price="180" class="in-stock">Hindi Grammar Workbook</li>
<li data-price="899" class="in-stock">Atlas of India</li>
<li data-price="450">Cricket Rules Explained</li>
</ul>
<button id="cheap">Under ₹500 (jQuery)</button>
<button id="stock">In stock and under ₹500 (plain JS)</button>
<p id="out"></p>
<script src="https://cdn.jsdelivr.net/npm/jquery@4.0.0/dist/jquery.min.js"></script>
<script>
const $books = $("#books li");
$("#cheap").on("click", function () {
$books.removeClass("pick");
const $cheap = $books
.filter(function (index, element) {
return Number($(element).attr("data-price")) < 500;
})
.addClass("pick");
$("#out").text($cheap.length + " books under ₹500");
});
document.querySelector("#stock").addEventListener("click", () => {
const books = [...document.querySelectorAll("#books li")];
books.forEach((li) => li.classList.remove("pick"));
const found = books.filter((li) => li.matches(".in-stock") && Number(li.dataset.price) < 500);
found.forEach((li) => li.classList.add("pick"));
document.querySelector("#out").textContent = `${found.length} books in stock under ₹500`;
});
</script>
</body>
</html>Picking by position
These methods choose elements by their position in the set. Positions start at 0.
| jQuery | Keeps | Plain JavaScript (on an array arr) |
|---|---|---|
.first() | the first element | arr[0] or arr.at(0) |
.last() | the last element | arr.at(-1) |
.eq(2) | the third element | arr[2] |
.eq(-1) | the last element (negative counts from the end) | arr.at(-1) |
.slice(1, 3) | elements at index 1 and 2 | arr.slice(1, 3) |
.even() | indexes 0, 2, 4... | arr.filter((el, i) => i % 2 === 0) |
.odd() | indexes 1, 3, 5... | arr.filter((el, i) => i % 2 === 1) |
.even() and .odd() were added in jQuery 3.5. Be careful with the names: because counting starts at 0, .even() gives the 1st, 3rd and 5th elements as a human would count them. CSS :nth-child(odd) counts from 1, so it matches those same elements.
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>Position methods</title>
<style>
table { border-collapse: collapse; }
td { border: 1px solid #ccc; padding: 0.3rem 0.6rem; }
.stripe { background: #f2f6fa; }
.first { font-weight: bold; }
.last { color: #b3261e; }
</style>
</head>
<body>
<h2>Pune to Mumbai: stops</h2>
<table>
<tr><td>Pune Junction</td><td>06:00</td></tr>
<tr><td>Shivajinagar</td><td>06:08</td></tr>
<tr><td>Lonavala</td><td>07:05</td></tr>
<tr><td>Karjat</td><td>07:45</td></tr>
<tr><td>Mumbai CSMT</td><td>09:15</td></tr>
</table>
<p id="out"></p>
<script src="https://cdn.jsdelivr.net/npm/jquery@4.0.0/dist/jquery.min.js"></script>
<script>
const $rows = $("tr");
$rows.even().addClass("stripe"); // rows 1, 3 and 5 as people count them
$rows.first().addClass("first");
$rows.eq(-1).addClass("last");
const middle = $rows.slice(1, -1).find("td:first-child").map(function () {
return $(this).text();
}).get();
$("#out").text("Stops on the way: " + middle.join(", "));
</script>
</body>
</html>.slice(1, -1) keeps every row except the first and last. Then .map() turns each row into a value, and .get() converts the result into a normal JavaScript array so you can call join().
has() and is()
.has(selector) keeps elements that contain a matching descendant. .is(selector) does not filter at all: it returns true if at least one element in the set matches, which is handy inside if.
$("li").has("ul"); // list items that contain a nested list
if ($("#agree").is(":checked")) { /* ... */ }
if ($(event.target).is("button.delete")) { /* ... */ }
// Plain JavaScript
[...document.querySelectorAll("li")].filter((li) => li.querySelector("ul"));
document.querySelectorAll("li:has(ul)"); // CSS :has() is native in modern browsers
if (document.querySelector("#agree").checked) { /* ... */ }
if (event.target.matches("button.delete")) { /* ... */ }Going back with end()
Each filtering call creates a new set. .end() returns to the previous set in the chain, which lets you style two groups in one statement:
$("#books li")
.filter(".in-stock").addClass("available")
.end() // back to all the books
.not(".in-stock").addClass("unavailable");Chains like this were very popular, but they are harder to read than two short statements. Use .end() sparingly and recognise it when you see it in old code.
Common mistakes
- Calling array methods on a
NodeList.querySelectorAll(...).filterisundefined. Spread it into an array first:[...list]. - Expecting
.is()to return a set. It returnstrueorfalse, so you cannot chain after it. - Off-by-one with
.even(). It uses 0-based indexes, so it picks what people call the odd-numbered rows. - Forgetting
.get()after.map(). Without it you have a jQuery object of strings, not an array.
Key points
.filter()keeps matches and.not()removes them; both take a selector or a function..first(),.last(),.eq()(negative allowed),.slice(),.even()and.odd()pick by position..has()keeps elements containing a match;.is()answers yes or no..map(...).get()turns a set into a plain array of values.- In plain JavaScript, spread the
NodeListinto an array and usefilter,slice,atandmatches().
Exercise
Make a list of six cities with a data-temp attribute holding today's temperature. Using jQuery, highlight the cities hotter than 35 degrees with .filter(), make the last city bold with .last(), and show the names of the first three cities in a paragraph using .slice(), .map() and .get().
Show solution
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>City temperatures</title>
<style>.hot { background: #ffd8cc; } .bold { font-weight: bold; }</style>
</head>
<body>
<ul id="cities">
<li data-temp="41">Jaipur</li>
<li data-temp="29">Kochi</li>
<li data-temp="38">Lucknow</li>
<li data-temp="31">Pune</li>
<li data-temp="36">Rohtak</li>
<li data-temp="27">Shillong</li>
</ul>
<p id="first-three"></p>
<script src="https://cdn.jsdelivr.net/npm/jquery@4.0.0/dist/jquery.min.js"></script>
<script>
const $cities = $("#cities li");
$cities.filter(function (i, el) {
return Number($(el).attr("data-temp")) > 35;
}).addClass("hot");
$cities.last().addClass("bold");
const names = $cities.slice(0, 3).map(function () {
return $(this).text();
}).get();
$("#first-three").text("First three: " + names.join(", "));
</script>
</body>
</html>Jaipur, Lucknow and Rohtak are highlighted; .slice(0, 3) keeps indexes 0 to 2, and .get() turns the mapped set into an array for join().
Quick Quiz
1. What does $("li").eq(-1) return?
Negative numbers count back from the end, and .eq() always returns a jQuery set.
2. What is the plain JavaScript equivalent of $(el).is(".active")?
matches() checks whether the element itself fits the selector.
3. Why does document.querySelectorAll("li").filter(fn) throw an error?
Spread the list ([...list]) or use Array.from(list) to get array methods.
4. Which elements does .even() keep from a set of five?
Indexes start at 0, so the "even" indexes are the first, third and fifth elements.
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
