Finding Elements
Before the electrician can wire a switch, they have to find the right wall. Everything in the DOM chapter starts with finding the element.
You've been using document.querySelector('#greeting') since the very first lesson, taking it on trust. This chapter is where JavaScript meets the page properly, and it starts right here: how to point at exactly the element you want.
The good news is that you already know most of it. You learned it in CSS.
The DOM, again
In Nesting and the DOM, you learned that the browser turns your HTML into a family tree called the DOM (Document Object Model). Every element is a node in that tree, with parents, children, and siblings.
JavaScript sees the page through that tree. Each element becomes an object you can read and change, with properties and methods, just like the objects from Objects. And the whole tree is available through one special object: document.
querySelector: find the first match
const heading = document.querySelector('h1');
const cart = document.querySelector('#cart');
const firstPrice = document.querySelector('.price');
const emailInput = document.querySelector('input[type="email"]');querySelector takes any CSS selector, exactly like the ones from Selectors, and returns the first element that matches. Type selectors, classes, ids, attributes, combinators, pseudo-classes: all of it works.
const firstNavLink = document.querySelector('nav a');
const checkedSize = document.querySelector('input[name="size"]:checked');It's like asking a librarian, "Can you find me the first book on the top shelf with a red spine?" They search from the top of the library down and hand you the first one that fits.
querySelectorAll: find every match
const prices = document.querySelectorAll('.price');
console.log(prices.length); // how many were foundquerySelectorAll returns all matching elements, in page order, in a list called a NodeList. It's not quite an array, but it has a length, you can read items by index (prices[0]), and you can loop over it:
for (const price of prices) {
console.log(price.textContent);
}
prices.forEach((price) => {
price.classList.add('highlight');
});What a NodeList doesn't have is map, filter, and the other methods from Array Methods. When you need those, convert it first:
const priceTexts = Array.from(prices).map((price) => price.textContent);getElementById: the classic
const cart = document.getElementById('cart'); // no # hereThis older method finds an element by its id only, and it's slightly faster. Notice there's no #, because it only ever looks at ids. Mixing that up (getElementById('#cart')) is a common slip that returns null. querySelector('#cart') does the same job with the same selector syntax as everything else, which is why this book mostly uses it.
When nothing matches: null
const missing = document.querySelector('.does-not-exist');
console.log(missing); // nullquerySelector returns null when nothing matches. (querySelectorAll returns an empty NodeList instead, with a length of 0.) The trouble starts when you use that null as if it were an element:
document.querySelector('.cart-count').textContent = '3';
// TypeError: Cannot read properties of null (reading 'textContent')This is the most common DOM error there is. When you see it, the selector didn't find anything. Check, in order:
- A typo?
.cart-countin JavaScript, butcart_countin the HTML? - Wrong symbol? A class needs
., an id needs#. - Too early? Is the script running before the element exists? Remember
deferfrom Adding JavaScript to a Page. - Created later? Maybe the element only appears after something else happens.
When an element is genuinely optional, check for it before using it:
const banner = document.querySelector('.holiday-banner');
if (banner) {
banner.hidden = false;
}Searching inside an element
querySelector isn't only a document method. Every element has it too, and then it only searches inside that element:
const card = document.querySelector('.product-card');
const cardTitle = card.querySelector('h3'); // only the h3 inside this cardThat's how you work with repeated components, like a page full of product cards that all contain an h3 and a .price. Find the card first, then look inside it.
Walking the family tree
Once you have an element, you can move to its relatives:
const price = document.querySelector('.price');
price.parentElement; // its parent
price.children; // its child elements
price.nextElementSibling; // the next element beside it
price.previousElementSibling; // the one before itAnd one of the most useful methods of all, closest, walks up the tree to find the nearest ancestor that matches a selector (checking the element itself first):
const price = document.querySelector('.price');
const card = price.closest('.product-card');"Starting from this price, go up until you find the card it belongs to." You'll use closest a lot with events, in Events.
Store what you find
Finding an element takes a little work. If you'll use it more than once, find it once and keep it in a const:
const status = document.querySelector('#status');
status.textContent = 'Loading...';
// ...later
status.textContent = 'Done!';It's faster, and it's easier to read: the variable name says what the element is for.
Try it
HTML
JavaScript
Preview
Console
Nothing logged yet. Anything you pass to console.log() shows up here.
Try it yourself
- Find the second nav link with
document.querySelectorAll('nav a')[1]and log its text. - Change
.does-not-existto a real selector from the HTML, then trydocument.getElementById('#cart')with the#, and see what comes back. - Starting from the second
.price, useclosestto find its card, then log that card'sh3text.
Check your understanding
Up next
You can find anything on the page. Next, you'll change it: its text, its attributes, its classes, and the data-* details you stored on it back in the HTML track. That's Changing Text, Attributes, and Classes.
