Definición
El DOM (Document Object Model) es la representación que el navegador construye en memoria a partir del HTML que recibe. Es un árbol de objetos donde cada etiqueta se convierte en un nodo con sus propiedades, sus hijos y sus eventos.
La distinción importa: el HTML es el texto que llegó; el DOM es lo que hay ahora. Si JavaScript añade un párrafo, ese párrafo está en el DOM pero no en el HTML original. Por eso "ver código fuente" y las herramientas de desarrollador enseñan cosas distintas.
El DOM es la puerta por la que JavaScript toca la página. Sin él, un script no podría cambiar nada de lo que ves.
Cómo funciona
Cuando llega el HTML, el navegador hace esto:
- Parseo: convierte el texto en el árbol de nodos, el DOM.
- CSSOM: hace lo mismo con las hojas de estilo.
- Árbol de renderizado: combina ambos y se queda con lo que se ve.
- Layout (reflow): calcula la posición y el tamaño de cada elemento.
- Paint: lo dibuja en pantalla.
Los dos últimos pasos son los caros, y son la clave del rendimiento:
- Reflow: recalcular geometría. Ocurre cuando cambia algo que afecta al tamaño o la posición: ancho, margen, insertar un elemento, o leer propiedades como
offsetHeight. - Repaint: volver a dibujar sin recalcular posiciones. Ocurre al cambiar un color o una sombra. Es bastante más barato.
Regla práctica: animar transform y opacity no dispara reflow; animar width, top o margin, sí. Por eso las animaciones bien hechas se hacen con transform.
Ejemplo práctico
En el gato que camina por el borde inferior de aeworks.tech tuve que decidir cómo moverlo. Dos opciones:
La primera, cambiar la propiedad left en cada fotograma. Funciona, y cada cambio de left obliga al navegador a recalcular la geometría: reflow sesenta veces por segundo.
La segunda, la que usé: mover con transform: translateX(). El navegador puede resolverlo en la capa de composición sin recalcular nada del resto de la página.
Y hay un segundo detalle de DOM en ese mismo componente que da más problemas de lo que parece. El código hacía esto:
pensando.parentNode.innerHTML = "";
pensando.parentNode.textContent = "Error"; // aquí parentNode ya era null
Al vaciar el contenido con innerHTML = "", el nodo de dentro se queda huérfano: sigue existiendo en memoria, pero ya no cuelga de nadie, así que su parentNode pasa a null. La segunda línea reventaba y el mensaje de error nunca aparecía. La solución era guardar la referencia al padre antes de vaciar.
Es el tipo de error que solo se entiende cuando tienes claro que el DOM es un árbol de verdad y que quitar una rama no borra las hojas.
El DOM virtual
React, Vue y compañía introducen una capa intermedia: mantienen una copia ligera del DOM en memoria, calculan qué ha cambiado y aplican solo esas diferencias al DOM real.
No es que sea mágicamente más rápido que tocar el DOM a mano —tocar el DOM bien a mano es más rápido—. Lo que hace es evitar que lo toques mal, agrupando cambios y escribiendo una sola vez. En un proyecto grande, eso gana casi siempre.
Errores comunes
- Modificar el DOM dentro de un bucle. Cien inserciones son cien oportunidades de reflow. Construye todo en un
DocumentFragmenty añádelo de una vez. - Alternar lectura y escritura. Leer
offsetWidthjusto después de escribir fuerza al navegador a recalcular ahí mismo. Agrupa: primero todas las lecturas, después todas las escrituras. innerHTMLcon datos del usuario. Es la puerta directa a un XSS. UsatextContentcuando solo quieras poner texto.- Buscar el mismo elemento una y otra vez. Guarda la referencia en una variable.
- No quitar los escuchadores de eventos. En una SPA, los eventos que no se limpian se acumulan y provocan fugas de memoria.
- Tocar el DOM antes de que exista. Si el script se ejecuta en el
<head>sindefer, el elemento que buscas todavía no está.
Cuándo importa
Siempre que escribas JavaScript para el navegador, aunque uses un framework: cuando algo va lento o se comporta raro, el diagnóstico está en el DOM.
Y muy especialmente en móvil. Un reflow que en un portátil no se nota, en un teléfono de gama media se traduce en una animación que va a tirones.