Aprende leyendo en orden

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
Qué elemento encuentra cada selector
Elemento body — se busca de arriba abajo
Elemento ul — su id es news
1.er li — su clase es notice
  • "li" y ".notice" — los dos dejan de buscar aquí
  • Su texto es «Nuevas tarifas de envío»
2.º li — sus clases son notice e important
  • ".important" — deja de buscar aquí
  • Su texto es «Mantenimiento imprevisto»
3.er li — su clase es notice
  • Su texto es «Novedades de otoño»
  • Ninguno de los tres selectores llega hasta aquí
Hay tres li con notice, pero ".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.

En la página de un listado de productos, obtén elementos de uno en uno usando nombres de etiqueta, clases e id según el caso. El HTML ya está definido con la asignación a document.body.innerHTML del principio.

① Busca el primer producto por su nombre de etiqueta y muestra su texto.

② Busca el producto por la clase que solo tiene el producto recomendado y muestra su texto.

③ Busca por su id el elemento que muestra la cantidad de productos y muestra su texto.

(Si se ejecuta correctamente, aparecerá una explicación.)

Editor JavaScript / TypeScript

Ejecutar el código para ver el resultado

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
Conviértelo en array antes de llamar a map
notices.map(...)Un NodeListno tiene mapSe lanza unTypeErrortitles nuncase crea[...notices].map(...)Un array con losmismos 3 elementosEl map del arrayrecorre cada uno["Nuevas tarifasde envío", …]3 textos
Si llamas a map directamente sobre el NodeList, se lanza un error; si antes lo conviertes en array, funciona. Llama a map y filter después de convertir el NodeList en array.

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.

En la página del detalle de una compra, lee todos los productos comprados a la vez y revisa cuántos son y cuántos cuestan 2,000 yenes o más. El HTML del detalle ya está definido con la asignación a document.body.innerHTML del principio.

① Obtén todos los elementos de productos del detalle y muestra cuántos hay.

② Recorre de uno en uno los elementos que obtuviste y muestra el texto de cada producto en orden.

③ Obtén todos los elementos de precio, conviértelos en array, quédate solo con los de 2,000 yenes o más y muestra cuántos son.

Editor JavaScript / TypeScript

Ejecutar el código para ver el resultado

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
De un atributo a un selector en texto
data-id="N-103"[data-id="N-103"]'[data-id="N-103"]'Atributo del linombre="valor"Entre corcheteses un selectorLleva ", así queva entre '
Si pones un atributo HTML entre corchetes, obtienes un selector, y para pasarlo a JavaScript lo envuelves en comillas simples. Las comillas dobles del valor y las simples de afuera cumplen funciones distintas.

[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.

SelectorCoincide conDevuelve en los avisos
liEtiqueta li1.er li (Nuevas tarifas…)
.importantTiene la clase important2.º li (Mantenimiento…)
#newsid es newsEl elemento ul
[data-id]Tiene atributo data-id1.er li (Nuevas tarifas…)
[data-id="N-103"]data-id es N-1033.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 ".

En el inventario de una papelería, obtén los productos que buscas a partir de su número de producto y de la marca de oferta. El HTML del inventario ya está definido con la asignación a document.body.innerHTML del principio.

① Busca el elemento con el número de producto P-203 y muestra su texto.

② Busca el elemento que tiene el atributo de oferta y muestra su texto.

③ Obtén todos los elementos que tienen el atributo de número de producto y muestra cuántos hay.

Editor JavaScript / TypeScript

Ejecutar el código para ver el resultado

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
Tres búsquedas y el texto que muestran
querySelector(…)?.textContent?? "Ninguno"Sin aviso urgente,busca ".urgent"Con aviso urgente,busca ".urgent"Con aviso urgente,busca "urgent"Recibe null;?. se detiene ahíEncuentra el liy lee su textoBusca la etiquetaurgent; da nullMuestra «Ninguno»Muestra «Servidorcaído»Existe, peromuestra «Ninguno»
Las columnas 1 y 3 pasan de null a «Ninguno». ?. no distingue entre un elemento que falta y un error de escritura en el selector.

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 ?..

En la página de detalle de un producto, lee las reseñas y el precio de oferta, que pueden no existir. El HTML del detalle del producto ya está definido con la asignación a document.body.innerHTML del principio.

① Muestra el texto del nombre del producto.

② Obtén todos los li de reseñas y, si no hay ninguno, muestra «Todavía no hay reseñas».

③ Muestra el texto del precio de oferta sin que se lance un error si el elemento no existe.

④ Si no hay precio de oferta, muestra en su lugar el texto del precio normal.

Editor JavaScript / TypeScript

Ejecutar el código para ver el resultado
QUIZ

Verificación de conocimientos

Responde cada pregunta una a una.

Pregunta 1Si hay 3 li con data-id, ¿qué devuelve querySelector("[data-id]")?

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?