Pregunta 1Justo después de const li = document.createElement("li");, ¿en qué parte de la página está li?
Crear, cambiar y eliminar elementos — createElement / classList / remove
createElement y append para agregar elementos, remove para quitarlos, classList y dataset para cambiar clases y atributos, y textContent para mostrar la entrada como texto.
Si asignas a innerHTML en cada cambio, como agregar un elemento a una lista, marcar uno como terminado o borrar otro, reconstruyes incluso los li que querías conservar. Y si asignas de la misma forma lo que escribe el usuario, cualquier etiqueta que venga mezclada, como <a>, también se convierte en elemento.
En este artículo verás cómo crear y quitar elementos, classList y dataset para cambiar clases y atributos, y cómo insertar la entrada del usuario como texto.
Crear y agregar elementos — createElement y append
Imagina que quieres agregar «Enviar la factura» al final de una lista de tareas. Si le sumas un texto a innerHTML y lo vuelves a asignar, todos los li que ya estaban en la lista se reconstruyen como elementos nuevos. Un li que guardaste antes en una variable con querySelector sigue apuntando al elemento viejo, que ya no está en la página, así que si cambias su texto a través de esa variable, lo que se ve no cambia.
createElement (un método que recibe un nombre de etiqueta y crea un elemento nuevo que todavía no está en ninguna parte del árbol DOM) se llama así: document.createElement("li"). El elemento que creas pasa a formar parte de la página cuando lo agregas a un elemento padre con append (un método que agrega el elemento que le pasas al final de los hijos del elemento que lo llama).
document.body.innerHTML = `<ul id="tasks"><li>Preparar la cotización</li><li>Compartir las notas de la reunión</li></ul>`;
const taskList = document.querySelector("#tasks");
// Crea un elemento li y le pone texto (todavía no está en la página)
const newTask = document.createElement("li");
newTask.textContent = "Enviar la factura";
console.log(newTask.outerHTML); // <li>Enviar la factura</li>
console.log(document.querySelectorAll("li").length); // 2
// Al agregarlo al final de los hijos del ul, pasa a formar parte de la página
taskList.append(newTask);
console.log(document.querySelectorAll("li").length); // 3
console.log(taskList.outerHTML);
// <ul id="tasks"><li>Preparar la cotización</li><li>Compartir las notas de la reunión</li><li>Enviar la factura</li></ul>
La variable newTask apunta al mismo li antes y después de append. Por eso, si asignas a newTask.textContent después de llamar a append, cambia el texto del tercer li de la página.
Marcar tareas como terminadas — classList y dataset
Ahora quieres agregar la clase done a una tarea terminada para que el CSS la muestre tachada. El atributo class de ese li es un solo texto, task urgent, así que si lo reescribes como done, también pierdes la clase task que el CSS usa para dar estilo a la fila.
classList (un objeto para manejar las clases de un elemento una por una) tiene add para agregar una palabra, remove para quitarla y toggle para agregarla si no está o quitarla si está. Los atributos distintos de class se escriben con setAttribute(nombre, valor), y los que empiezan con data-, a través de dataset (un objeto para leer y escribir los atributos data por su nombre).
document.body.innerHTML = `<ul><li class="task urgent" data-id="T-1">Preparar la cotización</li><li class="task urgent" data-id="T-2">Enviar la factura</li></ul>`;
const quote = document.querySelector('[data-id="T-1"]');
const invoice = document.querySelector('[data-id="T-2"]');
// classList agrega y quita una palabra a la vez (las demás clases se quedan)
quote.classList.add("done");
console.log(quote.outerHTML); // <li class="task urgent done" data-id="T-1">Preparar la cotización</li>
quote.classList.toggle("urgent"); // Ya está terminada, así que se quita urgent (como está, toggle la quita)
console.log(quote.outerHTML); // <li class="task done" data-id="T-1">Preparar la cotización</li>
// setAttribute reemplaza el valor completo (si lo usas en class, también borra task)
invoice.setAttribute("class", "done");
console.log(invoice.outerHTML); // <li class="done" data-id="T-2">Enviar la factura</li>
// Los atributos que empiezan con data- se escriben con dataset (doneAt se convierte en data-done-at)
invoice.dataset.doneAt = "9/10";
console.log(invoice.outerHTML);
// <li class="done" data-id="T-2" data-done-at="9/10">Enviar la factura</li>
El nombre que usas con dataset es el nombre del atributo sin data- y con la letra que sigue a cada guion restante en mayúscula. data-id se lee y escribe como dataset.id, y data-done-at, como dataset.doneAt; al asignarles un valor, el atributo se escribe en el HTML con su nombre original.
dataset no acepta nombres con guion
Si lees con el guion, como en dataset["done-at"], obtienes undefined en lugar de un error, así que el error de escritura pasa fácilmente desapercibido. Si asignas de esa forma, esa línea lanza un error. Aunque se llama SyntaxError, no es un error de sintaxis de tu código: ocurre durante la ejecución, así que las líneas anteriores sí se ejecutan.
Quitar elementos — remove y las referencias que quedan en variables
Supón que quieres sacar de la lista una tarea terminada. Si reescribes la lista con innerHTML, también tienes que volver a escribir en el texto HTML las clases y los atributos data que agregaste en la sección anterior, y si se te olvida uno solo, una tarea que conservaste pierde su marca de terminada.
remove (un método que separa de su padre al elemento que lo llama y lo quita del árbol DOM) se llama sin argumentos sobre el elemento que quieres borrar, como en doneItem.remove(). El classList.remove("done") de la sección anterior solo quita una clase del atributo class; es un método distinto de este, que quita el elemento completo.
document.body.innerHTML = `<ul id="tasks"><li data-id="T-1">Preparar la cotización</li><li data-id="T-2" class="done">Enviar la factura</li><li data-id="T-3">Compartir las notas de la reunión</li></ul>`;
const tasks = document.querySelectorAll("li");
// Busca el li terminado y lo quita del árbol DOM
const doneItem = document.querySelector(".done");
doneItem.remove();
console.log(document.querySelectorAll("li").length); // 2
// El elemento quitado y el NodeList que obtuviste antes siguen en sus variables
console.log(doneItem.textContent); // Enviar la factura
console.log(tasks.length); // 3 (los elementos que había en ese momento)
// Como sigue en la variable, append puede devolverlo al final de la lista
document.querySelector("#tasks").append(doneItem);
console.log(document.querySelectorAll("li").length); // 3
querySelectorAll solo devuelve los elementos que encontró en el momento en que lo llamaste; no sigue los cambios posteriores del DOM. El doneItem que quitaste también sigue en su variable, así que todavía puedes leer su texto o devolverlo a la lista con append.
Mostrar la entrada como texto — textContent e innerHTML
Piensa en una sección de reseñas de productos que muestra los comentarios que publican los usuarios. Los comentarios se guardan, y el mismo texto aparece también en las páginas de otros visitantes. Si un comentario contiene una etiqueta a (la etiqueta que crea un enlace, con el destino en su atributo href), al asignarlo a innerHTML el enlace falso que dejó quien lo publicó aparece en la página como un enlace real.
Un texto asignado a innerHTML se analiza como HTML, y sus etiquetas se convierten en elementos. Un texto asignado a textContent no se analiza, así que incluso < entra como un carácter normal. Poner la entrada del usuario en innerHTML no solo permite colar enlaces falsos: también puede provocar XSS (una vulnerabilidad en la que el código que un usuario mete en lo que escribe se ejecuta en las páginas de otros visitantes).
document.body.innerHTML = `<p class="review"></p>`;
const review = document.querySelector(".review");
// Texto que un usuario escribió en el campo de reseña (con un enlace falso mezclado)
const reviewText = '<a href="https://example.com/login">Vuelve a iniciar sesión</a> para continuar';
// En innerHTML, la etiqueta a se convierte en un elemento a real
review.innerHTML = reviewText;
console.log(document.querySelectorAll("a").length); // 1
// En textContent, las etiquetas entran como texto
review.textContent = reviewText;
console.log(document.querySelectorAll("a").length); // 0
console.log(review.outerHTML);
// <p class="review"><a href="https://example.com/login">Vuelve a iniciar sesión</a> para continuar</p>
- Su texto es «Vuelve a iniciar sesión»
- Al hacer clic, lleva a otro sitio
- Va después del elemento a
<a href="…">Vuelve a iniciar sesión</a> para continuar- 0 elementos a
Los < y > de outerHTML son la forma en que HTML escribe < y > como texto. Si los vuelves a leer con textContent, obtienes los caracteres originales. La tabla de abajo ordena las formas de cambiar elementos que viste en este artículo según lo que cambian.
| Qué cambias | Cómo se escribe | Qué tener en cuenta |
|---|---|---|
| El elemento | createElement / append / remove | No está en la página hasta append |
| Clases | classList.add / remove / toggle | setAttribute reemplaza todo el valor |
| Atributos data- | dataset.doneAt = "9/10" | Sin data-, mayúscula tras cada guion |
| Otros atributos | setAttribute("href", "/tasks/T-2") | Lo agrega o reemplaza su valor |
| Texto interior | textContent | Aquí va la entrada del usuario |
| HTML interior | innerHTML | Solo HTML que escribiste tú |
El código en el onerror de un img sí se ejecuta
Un <script> que pones en innerHTML no se ejecuta, pero si una etiqueta img, que muestra una imagen, se escribe como <img src="x" onerror="…">, onerror (un atributo con código que se ejecuta cuando falla la carga) sí se ejecuta. Que <script> no se ejecute no significa que sea seguro poner la entrada del usuario en innerHTML.
Verificación de conocimientos
Responde cada pregunta una a una.
Pregunta 2Si el atributo class de un li es task urgent, ¿qué valor tiene después de setAttribute("class", "done")?
Pregunta 3Después de p.textContent = "<b>Nuevo</b>";, ¿cuántos elementos b hay dentro de p?