Cuando abres una página web, el navegador lee el HTML y construye una representación interna de todos los elementos: el Document Object Model, o DOM. Es esa representación la que JavaScript puede leer y modificar en tiempo real para hacer que las páginas sean interactivas. Sin el DOM, JavaScript no podría cambiar lo que ves en pantalla, responder a tus clics ni actualizar el contenido dinámicamente.
En esta guía aprenderás qué es el DOM, cómo seleccionar elementos, cómo modificarlos y cómo responder a eventos del usuario.
Qué es el DOM
El DOM es una representación en forma de árbol de todos los elementos de una página HTML. Cada etiqueta HTML se convierte en un nodo del árbol, y los nodos tienen una jerarquía de padres, hijos y hermanos.
<!-- Este HTML... -->
<!DOCTYPE html>
<html>
<head>
<title>Mi página</title>
</head>
<body>
<h1 id="titulo">Hola mundo</h1>
<p class="descripcion">Una descripción</p>
<ul>
<li>Elemento 1</li>
<li>Elemento 2</li>
</ul>
</body>
</html>
// ...se convierte en este árbol de nodos:
// document
// └── html
// ├── head
// │ └── title ("Mi página")
// └── body
// ├── h1#titulo ("Hola mundo")
// ├── p.descripcion ("Una descripción")
// └── ul
// ├── li ("Elemento 1")
// └── li ("Elemento 2")
// El nodo raíz es 'document': el punto de entrada a todo el árbol
console.log(document.title); // "Mi página"
console.log(document.body); // el elemento <body>
console.log(document.documentElement); // el elemento <html>
JavaScript accede al DOM a través del objeto global document. Todo lo que puedes hacer con el DOM empieza desde ahí.
Seleccionar elementos
Antes de modificar un elemento, tienes que encontrarlo. El DOM ofrece varios métodos para seleccionar elementos, y en el desarrollo moderno hay dos que cubren casi todos los casos: querySelector y querySelectorAll.
// querySelector: devuelve el PRIMER elemento que coincide con el selector CSS
const titulo = document.querySelector('#titulo'); // por id
const parrafo = document.querySelector('.descripcion'); // por clase
const boton = document.querySelector('button'); // por etiqueta
const inputEmail = document.querySelector('input[type="email"]'); // por atributo
const primeroActivo = document.querySelector('.lista .item.activo'); // selector complejo
// Si no encuentra nada, devuelve null
const inexistente = document.querySelector('#no-existe');
console.log(inexistente); // null
// Siempre verifica que no sea null antes de usarlo:
if (inexistente) {
inexistente.textContent = "ahora existe";
}
// querySelectorAll: devuelve TODOS los elementos que coinciden
// Devuelve un NodeList (parecido a un array pero no exactamente)
const items = document.querySelectorAll('.item');
const parrafos = document.querySelectorAll('p');
const inputs = document.querySelectorAll('input[required]');
console.log(items.length); // número de elementos encontrados
// Recorrer el NodeList
items.forEach(item => {
console.log(item.textContent);
});
// Convertir a array para usar métodos como map, filter, reduce
const arrayItems = Array.from(items);
const textos = arrayItems.map(item => item.textContent);
// Spread también funciona:
const textos2 = [...items].map(item => item.textContent);
// Métodos clásicos (todavía útiles en ciertos casos)
const porId = document.getElementById('titulo'); // solo por id, más rápido
const porClase = document.getElementsByClassName('item'); // HTMLCollection, viva
const porEtiqueta = document.getElementsByTagName('p'); // HTMLCollection, viva
// La diferencia entre NodeList (querySelectorAll) y HTMLCollection (getElementsBy*):
// HTMLCollection es "viva": si añades elementos al DOM, se actualiza sola
// NodeList de querySelectorAll es "estática": captura el estado en el momento de la llamada
Leer y modificar contenido
// textContent: el texto plano del elemento (sin HTML)
const titulo = document.querySelector('h1');
console.log(titulo.textContent); // "Hola mundo"
titulo.textContent = "Nuevo título"; // modifica el texto
// innerHTML: el contenido HTML del elemento (puede incluir etiquetas)
const contenedor = document.querySelector('#contenedor');
console.log(contenedor.innerHTML);
// "<p>Un párrafo</p><span>Un span</span>"
contenedor.innerHTML = '<p>Nuevo contenido <strong>con énfasis</strong></p>';
// Reemplaza todo el contenido interior con el nuevo HTML
// ⚠️ innerHTML y seguridad: nunca insertes texto del usuario directamente
// Esto crea una vulnerabilidad XSS (Cross-Site Scripting):
const busqueda = obtenerInputDelUsuario();
contenedor.innerHTML = `Resultados para: ${busqueda}`; // ❌ PELIGROSO
// Usa textContent para texto que venga del usuario:
contenedor.textContent = `Resultados para: ${busqueda}`; // ✅ seguro
// outerHTML: incluye el propio elemento, no solo su contenido interior
console.log(titulo.outerHTML); // "<h1 id='titulo'>Nuevo título</h1>"
Leer y modificar atributos
// HTML de partida:
// <a id="enlace" href="https://ejemplo.com" target="_blank">Visitar</a>
// <img id="foto" src="avatar.jpg" alt="Foto de perfil">
// <input id="campo" type="text" disabled>
const enlace = document.querySelector('#enlace');
const foto = document.querySelector('#foto');
const campo = document.querySelector('#campo');
// Leer atributos
console.log(enlace.href); // "https://ejemplo.com"
console.log(enlace.target); // "_blank"
console.log(foto.src); // URL completa de la imagen
console.log(foto.alt); // "Foto de perfil"
// Modificar atributos directamente como propiedades
enlace.href = "https://nuevo-sitio.com";
foto.src = "nueva-foto.jpg";
foto.alt = "Nueva descripción";
// getAttribute / setAttribute: para atributos personalizados o los que no son propiedades
console.log(enlace.getAttribute('href')); // el valor del atributo HTML exacto
enlace.setAttribute('href', 'https://otro.com');
enlace.setAttribute('rel', 'noopener'); // añadir atributo nuevo
// removeAttribute: eliminar un atributo
campo.removeAttribute('disabled'); // habilitar el input
// hasAttribute: comprobar si existe un atributo
console.log(campo.hasAttribute('disabled')); // false (lo acabamos de eliminar)
// Atributos data-*: para guardar datos en el HTML
// <div id="tarjeta" data-usuario-id="42" data-rol="admin">...</div>
const tarjeta = document.querySelector('#tarjeta');
console.log(tarjeta.dataset.usuarioId); // "42" (data-usuario-id → dataset.usuarioId)
console.log(tarjeta.dataset.rol); // "admin"
tarjeta.dataset.estado = "activo"; // añade data-estado="activo"
Manipular clases CSS
Una de las operaciones más frecuentes en la manipulación del DOM es añadir, quitar o alternar clases CSS. La propiedad classList hace esto de forma muy cómoda.
// HTML: <div id="modal" class="modal">...</div>
const modal = document.querySelector('#modal');
// Añadir una clase
modal.classList.add('activo');
// Ahora el elemento tiene: class="modal activo"
// Añadir varias clases a la vez
modal.classList.add('visible', 'animado');
// Quitar una clase
modal.classList.remove('activo');
// toggle: añade si no está, quita si está
modal.classList.toggle('activo'); // si tenía 'activo', la quita; si no, la añade
// Muy útil para menús, modales, acordeones:
const menuBtn = document.querySelector('#menu-btn');
const menu = document.querySelector('#menu');
menuBtn.addEventListener('click', () => {
menu.classList.toggle('abierto'); // abre o cierra el menú con un clic
});
// contains: comprobar si tiene una clase
console.log(modal.classList.contains('activo')); // true o false
// replace: reemplazar una clase por otra
modal.classList.replace('modal-pequeño', 'modal-grande');
// La propiedad className (alternativa más antigua): string con todas las clases
console.log(modal.className); // "modal visible animado"
modal.className = "modal"; // reemplaza TODAS las clases a la vez
Manipular estilos CSS directamente
// La propiedad style permite leer y modificar estilos en línea
const caja = document.querySelector('.caja');
// Modificar estilos: las propiedades CSS usan camelCase en JavaScript
caja.style.backgroundColor = '#3498db'; // background-color
caja.style.fontSize = '18px'; // font-size
caja.style.marginTop = '20px'; // margin-top
caja.style.display = 'none'; // ocultar el elemento
caja.style.display = 'block'; // mostrar el elemento
// Leer estilos: style solo lee los estilos en línea, no los de la hoja de estilos
console.log(caja.style.backgroundColor); // solo si fue definido inline o con .style
// Para leer el estilo calculado final (incluyendo CSS externo):
const estilosCalculados = window.getComputedStyle(caja);
console.log(estilosCalculados.fontSize); // "18px"
console.log(estilosCalculados.backgroundColor); // "rgb(52, 152, 219)"
// Mejor práctica: en lugar de modificar estilos directamente,
// añade/quita clases CSS. Es más mantenible y separa mejor JS y CSS:
// ❌ Estilos mezclados en JavaScript
caja.style.opacity = '0';
caja.style.transform = 'translateY(-10px)';
caja.style.transition = 'all 0.3s ease';
// ✅ La animación en CSS, JavaScript solo gestiona la clase
// En CSS: .oculto { opacity: 0; transform: translateY(-10px); transition: all 0.3s; }
caja.classList.add('oculto');
Crear y eliminar elementos
// Crear un elemento nuevo
const nuevoParrafo = document.createElement('p');
nuevoParrafo.textContent = 'Este párrafo fue creado con JavaScript';
nuevoParrafo.classList.add('dinamico');
nuevoParrafo.id = 'parrafo-nuevo';
// Añadirlo al DOM
const contenedor = document.querySelector('#contenedor');
contenedor.appendChild(nuevoParrafo); // al final del contenedor
contenedor.prepend(nuevoParrafo); // al principio del contenedor
contenedor.append("texto y ", nuevoParrafo); // append acepta texto y nodos
// insertBefore: insertar antes de un elemento específico
const referencia = document.querySelector('#elemento-referencia');
contenedor.insertBefore(nuevoParrafo, referencia);
// insertAdjacentElement: control más preciso de la posición
// 'beforebegin': antes del elemento (fuera, como hermano anterior)
// 'afterbegin': al principio del interior del elemento
// 'beforeend': al final del interior del elemento (como appendChild)
// 'afterend': después del elemento (fuera, como hermano siguiente)
referencia.insertAdjacentElement('afterend', nuevoParrafo);
// Crear elementos con innerHTML (más rápido para HTML complejo)
const lista = document.querySelector('ul');
lista.innerHTML += '<li class="nuevo">Elemento añadido</li>';
// ⚠️ Usar innerHTML en un bucle es lento: reconstruye todo el DOM interior
// Mejor: acumular el HTML en un string y asignarlo una sola vez al final
// Construir una lista eficientemente
const frutas = ['Manzana', 'Banana', 'Naranja', 'Kiwi'];
const listaFrutas = document.querySelector('#lista-frutas');
// ✅ Crear fragmento para minimizar reflows del DOM
const fragmento = document.createDocumentFragment();
frutas.forEach(fruta => {
const li = document.createElement('li');
li.textContent = fruta;
fragmento.appendChild(li);
});
listaFrutas.appendChild(fragmento); // una sola inserción en el DOM
// Eliminar elementos
const elemento = document.querySelector('#a-eliminar');
// Forma moderna (IE no soportado)
elemento.remove();
// Forma clásica (a través del padre)
elemento.parentNode.removeChild(elemento);
// Eliminar todos los hijos de un elemento
const contenedor = document.querySelector('#contenedor');
contenedor.innerHTML = ''; // rápido pero puede causar fugas de memoria con listeners
// Mejor opción:
while (contenedor.firstChild) {
contenedor.removeChild(contenedor.firstChild);
}
// O con replaceChildren (moderno):
contenedor.replaceChildren();Eventos: responder a las acciones del usuario
Los eventos son el mecanismo por el que JavaScript responde a lo que hace el usuario: clics, teclas pulsadas, movimiento del ratón, envío de formularios, scroll.
// addEventListener: la forma correcta de añadir manejadores de eventos
const boton = document.querySelector('#mi-boton');
boton.addEventListener('click', function(evento) {
console.log('Botón clickeado');
console.log(evento.target); // el elemento que disparó el evento
console.log(evento.type); // "click"
console.log(evento.timeStamp); // cuándo ocurrió
});
// Con función flecha:
boton.addEventListener('click', (e) => {
e.target.textContent = 'Clickeado!';
});
// Eventos más habituales
const input = document.querySelector('input');
const form = document.querySelector('form');
const ventana = window;
// Teclado
input.addEventListener('keydown', e => console.log('Tecla presionada:', e.key));
input.addEventListener('keyup', e => console.log('Tecla soltada:', e.key));
input.addEventListener('input', e => console.log('Valor actual:', e.target.value));
input.addEventListener('change', e => console.log('Valor cambiado:', e.target.value));
input.addEventListener('focus', () => input.classList.add('enfocado'));
input.addEventListener('blur', () => input.classList.remove('enfocado'));
// Ratón
boton.addEventListener('mouseenter', () => boton.classList.add('hover'));
boton.addEventListener('mouseleave', () => boton.classList.remove('hover'));
boton.addEventListener('dblclick', () => console.log('Doble clic'));
// Formularios
form.addEventListener('submit', (e) => {
e.preventDefault(); // evitar que el formulario recargue la página
const datos = new FormData(form);
console.log(datos.get('email'));
});
// Scroll y resize
ventana.addEventListener('scroll', () => console.log('Scroll:', window.scrollY));
ventana.addEventListener('resize', () => console.log('Tamaño:', window.innerWidth));
// Carga del DOM
document.addEventListener('DOMContentLoaded', () => {
// El HTML está cargado y parseado, pero las imágenes pueden no estarlo aún
iniciarAplicacion();
});Delegación de eventos
En lugar de añadir un listener a cada elemento de una lista, puedes añadir uno solo al contenedor padre y usar event.target para saber en qué elemento hizo clic el usuario. Esto es más eficiente y funciona para elementos añadidos dinámicamente.
// ❌ Un listener por elemento (poco eficiente para listas largas)
document.querySelectorAll('.item').forEach(item => {
item.addEventListener('click', () => console.log(item.textContent));
});
// Si añades items dinámicamente, no tendrán el listener
// ✅ Delegación de eventos: un solo listener en el padre
const lista = document.querySelector('#lista');
lista.addEventListener('click', (evento) => {
// Comprobar si el clic fue en un .item
const item = evento.target.closest('.item');
if (!item) return; // clic en otro lugar dentro de la lista
console.log('Item clickeado:', item.textContent);
item.classList.toggle('seleccionado');
});
// closest() sube por el árbol del DOM buscando el ancestro más cercano
// que coincida con el selector. Es perfecto para delegación de eventos.Eliminar listeners
// Para eliminar un listener, necesitas una referencia a la función
// Las funciones anónimas no se pueden eliminar
// ❌ No se puede eliminar (función anónima)
boton.addEventListener('click', () => console.log('clic'));
boton.removeEventListener('click', () => console.log('clic')); // no funciona
// ✅ Guardar referencia a la función
function manejarClic() {
console.log('clic');
}
boton.addEventListener('click', manejarClic);
boton.removeEventListener('click', manejarClic); // funciona
// Opción moderna: AbortController para eliminar múltiples listeners a la vez
const controlador = new AbortController();
boton.addEventListener('click', manejarClic, { signal: controlador.signal });
input.addEventListener('keydown', manejarTecla, { signal: controlador.signal });
form.addEventListener('submit', manejarEnvio, { signal: controlador.signal });
// Eliminar todos a la vez
controlador.abort();Ejemplo completo: lista de tareas interactiva
// HTML necesario:
// <input id="nueva-tarea" placeholder="Nueva tarea...">
// <button id="btn-añadir">Añadir</button>
// <ul id="lista-tareas"></ul>
const inputTarea = document.querySelector('#nueva-tarea');
const btnAñadir = document.querySelector('#btn-añadir');
const listaTareas = document.querySelector('#lista-tareas');
// Añadir tarea
function añadirTarea() {
const texto = inputTarea.value.trim();
if (!texto) return; // no añadir tareas vacías
const li = document.createElement('li');
li.classList.add('tarea');
li.innerHTML = `
<span class="texto">${texto}</span>
<button class="btn-completar">✓</button>
<button class="btn-eliminar">✗</button>
`;
listaTareas.appendChild(li);
inputTarea.value = ''; // limpiar el input
inputTarea.focus(); // volver el foco al input
}
btnAñadir.addEventListener('click', añadirTarea);
// Añadir con Enter
inputTarea.addEventListener('keydown', (e) => {
if (e.key === 'Enter') añadirTarea();
});
// Gestionar acciones con delegación de eventos
listaTareas.addEventListener('click', (e) => {
const tarea = e.target.closest('.tarea');
if (!tarea) return;
if (e.target.classList.contains('btn-completar')) {
tarea.classList.toggle('completada');
}
if (e.target.classList.contains('btn-eliminar')) {
tarea.style.opacity = '0';
tarea.style.transition = 'opacity 0.3s';
setTimeout(() => tarea.remove(), 300); // animar antes de eliminar
}
});
Resumen
- El DOM es la representación en árbol de los elementos HTML que el navegador construye cuando carga una página. JavaScript accede a él a través del objeto global
document. querySelectorselecciona el primer elemento que coincide con un selector CSS.querySelectorAlldevuelve todos. Son los métodos de selección más versátiles y los recomendados en el desarrollo moderno.textContentlee y escribe el texto plano de un elemento.innerHTMLpermite HTML, pero nunca insertes texto del usuario directamente coninnerHTMLpor seguridad.classList.add,classList.removeyclassList.toggleson la forma correcta de manipular clases CSS. Modificar clases es mejor que modificarstyledirectamente.createElementcrea nuevos nodos yappendChild,prependoinsertAdjacentElementlos insertan en el DOM. Para muchos elementos, usaDocumentFragmentpara hacerlo en una sola operación.addEventListeneres la forma correcta de escuchar eventos. Soporta los mismos tipos que los atributosonclick, pero es más flexible y permite múltiples listeners por evento.- La delegación de eventos (un listener en el padre,
event.target.closest()para identificar el hijo) es más eficiente que un listener por elemento y funciona con elementos añadidos dinámicamente.
No hay comentarios todavía. Sé el primero en compartir tu opinión.