Skip to content

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) ​

textContentinnerHTML
Interpreta etiquetasNoSí
VelocidadRápidoMás lento
Seguro con datos del usuarioSí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 hijos

Insertar 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 DOM

La 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:

Manipulación del DOM

Un contador clásico: el estado vive en una variable y una función render actualiza el DOM:

Contador

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>
  1. Cambia el texto del <h1> a "Mi súper lista" y agrégale la clase destacado.
  2. Agrega "Huevos" al final de la lista y "Café" al inicio, usando createElement.
  3. Muestra en #total el texto "3 productos" (calculado contando los <li>).
  4. Escribe una función agregar(texto) que cree un <li> con el texto (de forma segura frente a XSS) y actualice el total.
  5. 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; document es la raíz.
  • Selecciona con querySelector / querySelectorAll usando selectores CSS; sube por el árbol con closest.
  • textContent para texto (seguro), innerHTML sólo para HTML que tú controlas.
  • Crea con createElement, inserta con append/prepend/before/after, elimina con remove.
  • Cambia estilos con classList; guarda datos en el HTML con data-* y dataset.
  • Patrón profesional: un estado + una función render.

Siguiente: Eventos

Academia Web — aprende, copia y construye.