Pregunta 1Si hay 3 li con data-id, ¿qué devuelve querySelector("[data-id]")?
El árbol DOM y cómo obtener elementos — querySelector y querySelectorAll
querySelector y querySelectorAll para obtener elementos del árbol DOM: búsqueda por clase, id y atributos data, cómo pasar un NodeList a un array y ?. cuando el elemento no existe.
Imagina que quieres leer el precio de todos los productos de un listado. Buscar por id, como con #total, solo sirve para elementos que tienen un id, así que no encaja con un listado de productos que comparten la misma estructura.
En este artículo verás los selectores, que buscan elementos con condiciones distintas del id, querySelectorAll, que reúne todas las coincidencias, y cómo leer un elemento que puede no existir.
Obtener un solo elemento — querySelector y los selectores
Ahora quieres solo el título del aviso importante dentro de una lista de avisos. La lista está formada por elementos li con la misma estructura, y ninguno tiene id. El id es un nombre que solo puede tener un elemento de la página, así que no sirve para marcar un tipo de elemento, como «avisos importantes».
Un selector (un texto que describe el elemento que buscas) indica el elemento por su nombre de etiqueta, como li, por su id, como #news, o por su clase (el valor del atributo class, un nombre que les pones a todos los elementos del mismo tipo), como .notice. document.querySelector(selector) devuelve el primer elemento del árbol DOM que coincide.
// Prepara una lista de avisos (un elemento puede tener varias clases separadas por espacios)
document.body.innerHTML = `<ul id="news">
<li class="notice">Nuevas tarifas de envío</li>
<li class="notice important">Mantenimiento imprevisto</li>
<li class="notice">Novedades de otoño</li>
</ul>`;
// Busca por nombre de etiqueta / por clase (el nombre de la clase va después del .)
console.log(document.querySelector("li").textContent); // Nuevas tarifas de envío
console.log(document.querySelector(".notice").textContent); // Nuevas tarifas de envío
console.log(document.querySelector(".important").textContent); // Mantenimiento imprevisto
"li"y".notice"— los dos dejan de buscar aquí- Su texto es «Nuevas tarifas de envío»
".important"— deja de buscar aquí- Su texto es «Mantenimiento imprevisto»
- Su texto es «Novedades de otoño»
- Ninguno de los tres selectores llega hasta aquí
".notice" deja de buscar en el primero. Lo que recibes es el primer elemento que coincide.El segundo li tiene dos clases, así que coincide tanto con .notice como con .important. Para obtener solo el segundo, escribe una condición que únicamente cumpla ese elemento, como .important.
Recorrer toda una lista — querySelectorAll y NodeList
¿Y si quieres los títulos de todos los avisos a la vez? querySelector deja de buscar en la primera coincidencia, así que por más veces que lo llames con el mismo selector, solo obtienes «Nuevas tarifas de envío» y nunca llegas al segundo ni a los siguientes.
document.querySelectorAll(selector) devuelve todos los elementos que coinciden en un NodeList (un objeto parecido a un array que guarda los elementos encontrados en el orden del árbol DOM). Con length sabes cuántos hay y con forEach los procesas de uno en uno.
document.body.innerHTML = `<ul id="news">
<li class="notice">Nuevas tarifas de envío</li>
<li class="notice important">Mantenimiento imprevisto</li>
<li class="notice">Novedades de otoño</li>
</ul>`;
// Obtiene todos los elementos que coinciden en un NodeList
const notices = document.querySelectorAll(".notice");
console.log(notices.length); // 3
notices.forEach((notice) => console.log(notice.textContent));
// Nuevas tarifas de envío
// Mantenimiento imprevisto
// Novedades de otoño
// notices.map(...) lanza un TypeError. Conviértelo en array antes de llamar a map
const titles = [...notices].map((notice) => notice.textContent);
console.log(titles.join(" / ")); // Nuevas tarifas de envío / Mantenimiento imprevisto / Novedades de otoño
Aunque siga siendo un NodeList, puedes leer un elemento por su índice, como notices[1]. El array que creas con la sintaxis spread es otro objeto, así que notices sigue siendo un NodeList después de la conversión y puedes seguir usándolo con forEach.
Apuntar a un elemento por su número — atributos data y selectores de atributo
Supón que necesitas solo el aviso con el número N-103. Si los mismos avisos aparecen también en otra sección de la página, los id se repetirían, así que el id no sirve. Y una clase es un nombre para agrupar elementos del mismo tipo, así que tampoco es buen lugar para guardar un número.
Un atributo data (un atributo cuyo nombre empieza con data-) puede guardar cualquier valor propio, como un número. Los selectores que buscan por atributo van entre corchetes: [data-id] coincide con los elementos que tienen ese atributo, y [data-id="N-103"], con los que además tienen ese valor.
document.body.innerHTML = `<ul id="news">
<li class="notice" data-id="N-101">Nuevas tarifas de envío</li>
<li class="notice important" data-id="N-102">Mantenimiento imprevisto</li>
<li class="notice" data-id="N-103">Novedades de otoño</li>
</ul>`;
// Busca solo por si el atributo existe (coinciden los 3)
console.log(document.querySelector("[data-id]").textContent); // Nuevas tarifas de envío
// Busca un elemento cuyo valor también coincida (el valor va entre ", así que todo el selector va entre ')
console.log(document.querySelector('[data-id="N-103"]').textContent); // Novedades de otoño
[data-id], sin valor, coincide con los tres y devuelve el primero, mientras que [data-id="N-103"], con valor, apunta solo al que tiene ese número. La tabla de abajo reúne los selectores de este artículo y el elemento que devuelve cada uno en la lista de avisos.
| Selector | Coincide con | Devuelve en los avisos |
|---|---|---|
| li | Etiqueta li | 1.er li (Nuevas tarifas…) |
| .important | Tiene la clase important | 2.º li (Mantenimiento…) |
| #news | id es news | El elemento ul |
| [data-id] | Tiene atributo data-id | 1.er li (Nuevas tarifas…) |
| [data-id="N-103"] | data-id es N-103 | 3.er li (Novedades…) |
Sin comillas, los valores que empiezan con un dígito fallan
Algunos valores funcionan sin comillas, como [data-id=N-103], pero un valor que empieza con un dígito, como [data-id=102], no se puede leer como selector y la línea lanza un error «is not a valid selector». Escribe siempre los valores entre ".
Seguir adelante cuando falta el elemento — null y ?.
Piensa en una página de inicio que muestra un título solo cuando hay un aviso urgente. Los días en que no lo hay, el elemento no existe, así que la línea que lee el texto del título lanza un TypeError y no se ejecuta nada de lo que viene después.
Cuando ningún elemento coincide, querySelector devuelve null. Si agregas el encadenamiento opcional ?. al resultado antes de leer .textContent, cuando el valor es null devuelve undefined sin seguir leyendo, y si le agregas ?? después, puedes definir un valor por defecto.
// Una lista que todavía no tiene aviso urgente (clase urgent)
document.body.innerHTML = `<ul><li class="notice">Novedades de otoño</li></ul>`;
const urgent = document.querySelector(".urgent");
console.log(urgent); // null
// urgent.textContent lanzaría un TypeError
console.log(urgent?.textContent); // undefined
console.log(urgent?.textContent ?? "Ninguno"); // Ninguno
// En una lista que sí tiene aviso urgente, compara con y sin el .
document.body.innerHTML = `<ul><li class="urgent">Servidor caído</li></ul>`;
console.log(document.querySelector(".urgent")?.textContent ?? "Ninguno"); // Servidor caído
console.log(document.querySelector("urgent")?.textContent ?? "Ninguno"); // Ninguno
Usa ?. solo en elementos que algunos días pueden faltar, como el aviso urgente. Si lo pones también en elementos que siempre están, no vas a notar los errores de escritura. En esos casos, mejor no lo pongas: deja que el código lance un TypeError y usa el número de línea para encontrar la causa.
Un NodeList con 0 elementos no es null
Aunque nada coincida, querySelectorAll no devuelve null, sino un NodeList cuyo length es 0. Si llamas a forEach sobre él, no se lanza ningún error y simplemente no pasa nada; para saber que no hubo coincidencias, revisa length === 0 en lugar de usar ?..
Verificación de conocimientos
Responde cada pregunta una a una.
Pregunta 2¿Qué pasa si llamas a map directamente sobre el valor que devuelve querySelectorAll(".notice")?
Pregunta 3Si el elemento no existe, ¿cuál es el valor de querySelector(".sale-price")?.textContent?