Why traverse?
Selectors search the whole page. Very often, though, you already have one element, typically the button someone just clicked, and you need something related to it: the card it sits in, the row of the table, the input next to it. Moving from one element to its relatives is called traversing the DOM.
This lesson goes up and down the tree. The next goes sideways.
The family tree
Here is the HTML used in the examples:
<div class="product" id="p1">
<h3 class="name">Desk lamp</h3>
<div class="details">
<span class="price">1200</span>
<button class="add">Add to cart</button>
</div>
</div>The button is a child of .details, which is a child of .product. Going the other way, .product is the button's ancestor, and the button is a descendant of .product.
Going up
<div class="product" id="p1">
<h3 class="name">Desk lamp</h3>
<div class="details">
<span class="price">1200</span>
<button class="add">Add to cart</button>
</div>
</div>
<script src="https://code.jquery.com/jquery-4.0.0.min.js"></script>
<script>
const $button = $(".add");
console.log("parent:", $button.parent().attr("class"));
console.log("parents:", $button.parents().map((i, el) => el.tagName.toLowerCase() + (el.className ? "." + el.className : "")).get().join(" < "));
console.log("parents('.product'):", $button.parents(".product").attr("id"));
console.log("closest('.product'):", $button.closest(".product").attr("id"));
console.log("closest('button'):", $button.closest("button").attr("class"));
</script>parent: details
parents: div.details < div.product < body < html
parents('.product'): p1
closest('.product'): p1
closest('button'): add.parent()moves one level up..parents()returns every ancestor, up tohtml. Give it a selector to keep only matching ones..closest(selector)walks up from the element itself and stops at the first match. It returns at most one element per starting element.
.closest() is the one you will use most, because it does not care how deep the button is buried.
The click pattern
Here is the real reason traversal matters. Several products share the same button class, and each click must affect only its own product:
<div class="product" id="p1"><h3 class="name">Desk lamp</h3><button class="add">Add</button></div>
<div class="product" id="p2"><h3 class="name">Notebook</h3><button class="add">Add</button></div>
<div class="product" id="p3"><h3 class="name">Pen set</h3><button class="add">Add</button></div>
<p id="log"></p>
<script src="https://code.jquery.com/jquery-4.0.0.min.js"></script>
<script>
$(".add").on("click", function () {
const $product = $(this).closest(".product");
const name = $product.find(".name").text();
$product.css("outline", "2px solid green");
$("#log").text("Added: " + name);
console.log("clicked in", $product.attr("id"));
});
</script>clicked in p2this is the button that was clicked. From it, .closest(".product") reaches its own card, and .find(".name") reads the name inside that card only. This "up to the container, then down to what you need" move appears in nearly every interactive page.
Going down
<ul id="menu">
<li>Home</li>
<li>Shop
<ul>
<li>Lamps</li>
<li>Desks</li>
</ul>
</li>
<li>Contact</li>
</ul>
<script src="https://code.jquery.com/jquery-4.0.0.min.js"></script>
<script>
const $menu = $("#menu");
console.log("children():", $menu.children().length);
console.log("children('li'):", $menu.children("li").length);
console.log("find('li'):", $menu.find("li").length);
console.log("find('ul li'):", $menu.find("ul li").text());
</script>children(): 3
children('li'): 3
find('li'): 5
find('ul li'): LampsDesks.children()returns direct children only: the three top-level items..find(selector)searches all levels below and needs a selector: it found all five list items.
Why not count parents?
You will see code like this in old projects:
const $row = $(this).parent().parent();It works until someone wraps the button in a <span> for styling. Then .parent().parent() lands on the wrong element and the code fails, often silently. $(this).closest("tr") keeps working because it searches until it finds a row. Write traversal that describes what you want, not how many steps away it is.
Plain JavaScript equivalents
The browser has matching tools, useful when you read modern code:
| jQuery | Plain JavaScript |
|---|---|
$(el).parent() |
el.parentElement |
$(el).closest(".card") |
el.closest(".card") |
$(el).children() |
el.children |
$(el).find(".name") |
el.querySelectorAll(".name") |
The jQuery versions work on many elements at once and never throw when nothing matches.
Common mistakes
- Using
.parents()when you want one element. It can return several; use.closest(). - Calling
.find()with no selector, which returns nothing. - Using
.children()to reach deeply nested elements. It only looks one level down. - Selecting from the whole page inside a click handler, such as
$(".name"), which reads every product's name instead of the clicked one.
Exercise
You have a table where each row has a name cell and a Delete button. Write a click handler that logs the name in the same row as the button that was clicked.
Show one possible answer
<table>
<tr><td class="who">Asha</td><td><button class="del">Delete</button></td></tr>
<tr><td class="who">Ben</td><td><button class="del">Delete</button></td></tr>
</table>
<script src="https://code.jquery.com/jquery-4.0.0.min.js"></script>
<script>
$(".del").on("click", function () {
const name = $(this).closest("tr").find(".who").text();
console.log(name);
});
</script>Key points
- .parent() goes up one level; .parents() returns every ancestor; .closest(selector) returns the nearest ancestor that matches, starting with the element itself.
- .children() returns direct children only; .find(selector) searches every level below.
- Traversing is how you find the right card, row or form for the button that was clicked.
- Prefer .closest() and .find() over long chains of .parent().parent(), which break when the HTML changes.