Manejo del DOM con JavaScript
Todo lo que necesitas saber para interactuar con el Document Object Model (DOM) de forma eficiente, segura y mantenible.
¿Qué es el DOM?
El Document Object Model es una representación estructurada del documento HTML o XML que el navegador crea al cargar una página. Cada elemento, atributo y texto del documento se expone como un nodo JavaScript, permitiendo su inspección y modificación en tiempo real.
- Árbol jerárquico de nodos.
- Interfaz estándar (W3C) soportada por todos los navegadores modernos.
- Puente entre HTML estático y la lógica dinámica del frontend.
Acceso al DOM
Los métodos de document permiten obtener referencias a nodos. A continuación, los más usados:
// Selección por ID (más rápido)
const header = document.getElementById('main-header');
// Selección CSS con querySelector (flexible)
const firstCard = document.querySelector('.card');
// Selección múltiple con querySelectorAll (NodeList)
const items = document.querySelectorAll('ul > li');
Consejo: usa getElementById siempre que sea posible; es O(1) frente a la búsqueda CSS que es O(n).
Manipulación del DOM
Una vez que tienes la referencia al nodo, puedes cambiar su contenido, atributos, estilo o incluso crear nodos nuevos.
// Cambiar texto sin riesgo de XSS
header.textContent = 'Nuevo Título';
// Insertar HTML (cuidado con XSS)
header.innerHTML = '<span class="highlight">Nuevo</span> Título';
// Crear y añadir un elemento
const btn = document.createElement('button');
btn.type = 'button';
btn.className = 'btn btn-primary';
btn.textContent = 'Click me';
header.appendChild(btn);
Preferencia: textContent > innerHTML cuando sólo necesitas texto, ya que evita la ejecución accidental de scripts.
Gestión de eventos
Los eventos permiten reaccionar a la interacción del usuario. El patrón recomendado es addEventListener para mantener la separación de responsabilidades.
btn.addEventListener('click', (e) => {
e.preventDefault();
alert('Botón pulsado');
});
Usa delegación de eventos cuando trabajes con listas dinámicas:
document.querySelector('#lista').addEventListener('click', (e) => {
if (e.target && e.target.matches('li.item')) {
console.log('Item seleccionado:', e.target.textContent);
}
});
Comparativa: Manipulación directa vs. Frameworks modernos
Manipulación directa (Vanilla JS)
- Control total del ciclo de vida del nodo.
- Menor carga de recursos (no dependencias).
- Mayor riesgo de errores de sincronización en UI complejas.
- Ideal para widgets, extensiones y páginas estáticas.
Frameworks (React, Vue, Svelte)
- Virtual DOM o compilación que reduce manipulaciones reales.
- Abstracción de estado y renderizado declarativo.
- Mayor peso de bundle (aunque optimizable).
- Escalabilidad y mantenibilidad en aplicaciones SPA.
Rendimiento y buenas prácticas
- Batch DOM updates: Agrupa cambios usando
DocumentFragmentorequestAnimationFramepara evitar reflows/repaints continuos. - Minimiza consultas: Cachea referencias a nodos en variables locales.
- Lazy‑load contenido: Usa
IntersectionObserverpara cargar imágenes o componentes bajo demanda. - Evita layouts thrashing: No leas propiedades de layout (e.g.,
offsetHeight) después de escribir al DOM sin una pausa.
Seguridad: Prevención de XSS
Cuando insertemos contenido proveniente del usuario, nunca utilices innerHTML sin sanitizar.
function escapeHTML(str) {
const div = document.createElement('div');
div.appendChild(document.createTextNode(str));
return div.innerHTML;
}
header.innerHTML = escapeHTML(userInput);
Alternativas seguras: textContent, librerías de sanitización como DOMPurify, o frameworks que escapan automáticamente.
Depuración y troubleshooting
- DevTools Elements panel: Inspecciona y edita el DOM en tiempo real.
- Breakpoints en mutaciones: En Chrome, usa el panel "Sources → Event Listener Breakpoints → DOM Mutation".
- Console utilities:
console.dir(node)muestra la estructura completa del nodo. - Performance tab: Analiza reflows y paints para identificar cuellos de botella.
Ejemplo completo: Lista de tareas interactiva
<section id="todo-app" class="p-4">
<h3>Lista de tareas</h3>
<input id="new-task" type="text" placeholder="Nueva tarea" class="form-control mb-2">
<ul id="tasks" class="list-group"></ul>
</section>
const input = document.getElementById('new-task');
const list = document.getElementById('tasks');
function addTask(text) {
const li = document.createElement('li');
li.className = 'list-group-item d-flex justify-content-between align-items-center';
li.textContent = text;
const delBtn = document.createElement('button');
delBtn.className = 'btn btn-sm btn-outline-danger';
delBtn.textContent = '✕';
delBtn.addEventListener('click', () => li.remove());
li.appendChild(delBtn);
list.appendChild(li);
}
input.addEventListener('keypress', (e) => {
if (e.key === 'Enter' && input.value.trim()) {
addTask(input.value.trim());
input.value = '';
}
});
Este ejemplo muestra buenas prácticas: separación de lógica, uso de textContent, delegación mínima, y estilos Bootstrap para UI limpia.
Manejo del DOM con JavaScript: Guía completa, ejemplos y mejores prácticas