Tema
El DOM
Hasta ahora tu código vivía en la consola. Con el DOM JavaScript puede leer y cambiar la página: mostrar resultados, agregar productos a una lista, ocultar un menú, cambiar colores. Es el puente entre tu lógica y lo que el usuario ve. Dominarlo es lo que convierte a JavaScript en una herramienta para construir interfaces.
Concepto
Cuando el navegador carga tu HTML, lo convierte en un árbol de objetos llamado DOM (Document Object Model). Cada etiqueta es un nodo con padre, hijos y hermanos, como un árbol genealógico.
html
<body>
<header>
<h1>Mi tienda</h1>
</header>
<ul id="productos">
<li>Mouse</li>
<li>Teclado</li>
</ul>
</body>txt
document
└── html
└── body
├── header
│ └── h1 ("Mi tienda")
└── ul#productos
├── li ("Mouse")
└── li ("Teclado")El objeto global document es la raíz del árbol. Desde él puedes encontrar cualquier elemento, modificarlo, crear nuevos o eliminarlos. Cuando cambias el DOM, el navegador vuelve a pintar la página automáticamente.
Seleccionar elementos
js
// Un elemento: el PRIMERO que coincida con el selector CSS (o null)
const titulo = document.querySelector('h1');
const lista = document.querySelector('#productos');
const boton = document.querySelector('.btn-primario');
const input = document.querySelector('input[name="email"]');
// Todos los que coincidan: devuelve un NodeList (parecido a un array)
const items = document.querySelectorAll('#productos li');
items.forEach((li) => console.log(li.textContent));
// Convertir a array real para usar map/filter
const textos = [...items].map((li) => li.textContent);
// Métodos clásicos (siguen siendo válidos y rápidos)
document.getElementById('productos'); // sin '#'
document.getElementsByClassName('tarjeta'); // HTMLCollection "en vivo"
// Buscar dentro de otro elemento
const primerItem = lista.querySelector('li');
// Buscar hacia ARRIBA el ancestro más cercano que coincida
const tarjeta = boton.closest('.tarjeta');Error común: el script corre antes que el HTML
Si tu <script> está en el <head> sin defer, querySelector devuelve null porque el HTML aún no existe. Solución: pon el script al final del <body>, o usa <script src="app.js" defer> (recomendado). Lo viste en Incluir JS y la consola.
Leer y cambiar contenido
js
const titulo = document.querySelector('h1');
// textContent: sólo texto (seguro)
titulo.textContent = 'Ofertas de la semana';
// innerHTML: interpreta HTML
const caja = document.querySelector('.caja');
caja.innerHTML = '<strong>¡Nuevo!</strong> 20% de descuento';
// Valor de los campos de formulario
const email = document.querySelector('#email');
console.log(email.value);
email.value = 'ana@correo.com';textContent vs innerHTML (y el peligro de XSS)
textContent | innerHTML | |
|---|---|---|
| Interpreta etiquetas | No | Sí |
| Velocidad | Rápido | Más lento |
| Seguro con datos del usuario | Sí | No |
Si el usuario escribe <img src=x onerror="robarCookies()"> en un comentario y tú lo insertas con innerHTML, ese código se ejecuta en el navegador de todos los que vean la página. Esto se llama XSS (Cross-Site Scripting) y es una de las vulnerabilidades más comunes.
js
const comentario = obtenerComentarioDelUsuario();
// ❌ Peligroso
contenedor.innerHTML = `<p>${comentario}</p>`;
// ✅ Seguro: crear el elemento y usar textContent
const p = document.createElement('p');
p.textContent = comentario;
contenedor.append(p);Regla de oro
Usa innerHTML sólo con HTML que tú controlas (plantillas fijas). Todo dato que venga del usuario o de una API externa va con textContent, o escapado.
Crear, insertar y eliminar elementos
js
// 1. Crear
const li = document.createElement('li');
li.textContent = 'Monitor';
li.classList.add('item');
// 2. Insertar
const lista = document.querySelector('#productos');
lista.append(li); // al final (acepta varios nodos y texto)
lista.prepend(li); // al inicio
titulo.before(nuevoNodo); // antes del elemento (como hermano)
titulo.after(nuevoNodo); // después
// insertAdjacentHTML: insertar HTML sin borrar lo existente
lista.insertAdjacentHTML('beforeend', '<li>Bocina</li>');
// posiciones: 'beforebegin', 'afterbegin', 'beforeend', 'afterend'
// 3. Eliminar
li.remove();
// 4. Reemplazar
viejo.replaceWith(nuevo);
// 5. Vaciar un contenedor
lista.replaceChildren(); // o lista.innerHTML = ''
// 6. Clonar (por ejemplo, una plantilla)
const copia = li.cloneNode(true); // true = con hijosInsertar muchos elementos eficientemente
Cada inserción en el DOM puede provocar un recálculo de estilos. Si vas a agregar 100 elementos, júntalos primero en un DocumentFragment:
js
const nombres = ['Ana', 'Luis', 'María', 'Carlos'];
const fragmento = document.createDocumentFragment();
nombres.forEach((nombre) => {
const li = document.createElement('li');
li.textContent = nombre;
fragmento.append(li);
});
lista.append(fragmento); // una sola inserción en el DOMLa etiqueta <template>
Para estructuras repetidas (tarjetas de producto, filas), define el HTML una vez en un <template> y clónalo:
html
<template id="tpl-tarjeta">
<article class="tarjeta">
<h3 class="tarjeta-titulo"></h3>
<p class="tarjeta-precio"></p>
<button class="tarjeta-btn">Agregar</button>
</article>
</template>
<div id="catalogo"></div>js
const tpl = document.querySelector('#tpl-tarjeta');
const catalogo = document.querySelector('#catalogo');
function crearTarjeta(producto) {
const nodo = tpl.content.cloneNode(true); // clona el contenido
nodo.querySelector('.tarjeta-titulo').textContent = producto.nombre;
nodo.querySelector('.tarjeta-precio').textContent = `$${producto.precio}`;
nodo.querySelector('.tarjeta-btn').dataset.id = producto.id;
return nodo;
}
catalogo.append(crearTarjeta({ id: 1, nombre: 'Mouse', precio: 349 }));Clases CSS con classList
La forma profesional de cambiar estilos es añadir o quitar clases, y dejar el diseño en el CSS.
js
const menu = document.querySelector('.menu');
menu.classList.add('abierto');
menu.classList.remove('abierto');
menu.classList.toggle('abierto'); // si la tiene la quita, si no la pone
menu.classList.toggle('abierto', true); // fuerza agregarla
menu.classList.contains('abierto'); // true / false
menu.classList.replace('claro', 'oscuro');css
.menu { display: none; }
.menu.abierto { display: block; }Estilos en línea
js
const caja = document.querySelector('.caja');
caja.style.backgroundColor = '#6d28d9'; // camelCase en vez de guiones
caja.style.setProperty('--color-acento', 'tomato'); // variables CSS
// Leer el estilo calculado final (incluye el CSS de las hojas de estilo)
getComputedStyle(caja).width; // '320px'Buena práctica
Usa style sólo para valores realmente dinámicos (una posición calculada, un porcentaje de progreso, una variable CSS). Para estados ("activo", "oculto", "error") usa clases.
Atributos y dataset
js
const enlace = document.querySelector('a');
enlace.getAttribute('href');
enlace.setAttribute('target', '_blank');
enlace.removeAttribute('title');
enlace.hasAttribute('download');
// Propiedades directas (más cómodas)
const img = document.querySelector('img');
img.src = '/img/producto.webp';
img.alt = 'Foto del producto';
const check = document.querySelector('#acepto');
check.checked = true;
boton.disabled = true;Los atributos data-* sirven para guardar datos propios en el HTML. Se leen con dataset (en camelCase):
html
<button class="agregar" data-id="42" data-precio-unitario="349">Agregar</button>js
const btn = document.querySelector('.agregar');
btn.dataset.id; // '42' (siempre string)
btn.dataset.precioUnitario; // '349'
btn.dataset.estado = 'agregado'; // crea data-estado="agregado"Recorrer el árbol
js
const item = document.querySelector('li');
item.parentElement; // el padre (<ul>)
item.children; // hijos (sólo elementos)
item.firstElementChild;
item.lastElementChild;
item.nextElementSibling; // hermano siguiente
item.previousElementSibling;
item.closest('section'); // ancestro más cercano que coincida
item.matches('.activo'); // ¿coincide con el selector?Demos en vivo
Agrega, edita y elimina tarjetas dinámicamente con createElement, textContent, classList y remove:
Un contador clásico: el estado vive en una variable y una función render actualiza el DOM:
Patrón profesional: estado + render
En lugar de modificar el DOM en mil lugares, guarda los datos en una variable (el estado) y escribe una sola función que dibuje la interfaz a partir de él. Es la idea detrás de React y Vue.
js
// Estado: la única fuente de verdad
let tareas = [
{ id: 1, texto: 'Diseñar logo', hecha: true },
{ id: 2, texto: 'Maquetar landing', hecha: false }
];
const lista = document.querySelector('#tareas');
// Render: estado → DOM
function render() {
lista.replaceChildren(
...tareas.map((t) => {
const li = document.createElement('li');
li.textContent = t.texto;
li.classList.toggle('hecha', t.hecha);
return li;
})
);
}
// Para cambiar la UI: cambia el estado y vuelve a renderizar
function agregarTarea(texto) {
tareas = [...tareas, { id: Date.now(), texto, hecha: false }];
render();
}
render();Ejercicios
Usa este HTML:
html
<h1 id="titulo">Lista de compras</h1>
<input id="nuevo" placeholder="Producto">
<ul id="compras">
<li>Leche</li>
<li>Pan</li>
</ul>
<p id="total"></p>- Cambia el texto del
<h1>a "Mi súper lista" y agrégale la clasedestacado. - Agrega "Huevos" al final de la lista y "Café" al inicio, usando
createElement. - Muestra en
#totalel texto"3 productos"(calculado contando los<li>). - Escribe una función
agregar(texto)que cree un<li>con el texto (de forma segura frente a XSS) y actualice el total. - Marca en rojo (con una clase
.importante) todos los elementos cuyo texto contenga la letra "a".
Ver solución
js
// 1
const titulo = document.querySelector('#titulo');
titulo.textContent = 'Mi súper lista';
titulo.classList.add('destacado');
// 2
const compras = document.querySelector('#compras');
const crearItem = (texto) => {
const li = document.createElement('li');
li.textContent = texto;
return li;
};
compras.append(crearItem('Huevos'));
compras.prepend(crearItem('Café'));
// 3 y 4
const total = document.querySelector('#total');
function actualizarTotal() {
const n = compras.children.length;
total.textContent = `${n} ${n === 1 ? 'producto' : 'productos'}`;
}
function agregar(texto) {
compras.append(crearItem(texto)); // textContent: seguro
actualizarTotal();
}
actualizarTotal();
// 5
document.querySelectorAll('#compras li').forEach((li) => {
li.classList.toggle('importante', li.textContent.toLowerCase().includes('a'));
});Resumen
- El DOM es el árbol de objetos que representa tu HTML;
documentes la raíz. - Selecciona con
querySelector/querySelectorAllusando selectores CSS; sube por el árbol conclosest. textContentpara texto (seguro),innerHTMLsólo para HTML que tú controlas.- Crea con
createElement, inserta conappend/prepend/before/after, elimina conremove. - Cambia estilos con
classList; guarda datos en el HTML condata-*ydataset. - Patrón profesional: un estado + una función
render.
Siguiente: Eventos