Aprende leyendo en orden

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>
Cómo llega a la página un li nuevo
createElement("li")newTask.textContent ="Enviar la factura"taskList.append(newTask)Crea <li></li>;2 li en la página<li>Enviar lafactura</li>aún fueraQueda al final;ya hay 3 li
Después de las líneas de createElement y textContent, sigue habiendo solo 2 li. La cantidad aumenta en la línea que llama a append.

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.

Agrega de una vez a la lista de consultas pendientes las consultas que acaban de llegar. El elemento ul de la lista, inquiryList, y el array con los asuntos de las consultas, newInquiries, ya están declarados.

① Convierte cada asunto del array en un elemento li y agrégalo al final de la lista.

② Muestra cuántos elementos li hay en la página.

③ Muestra el HTML de la lista, incluidas las etiquetas del propio ul.

(Si se ejecuta correctamente, aparecerá una explicación.)

Editor JavaScript / TypeScript

Ejecutar el código para ver el resultado

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>
add agrega, setAttribute reemplaza
quote.classList.add("done")Suma la palabradone a task urgentclass queda entask urgent doneConserva el estilode la clase taskinvoice.setAttribute("class", "done")Reemplaza todoel valor de classclass quedasolo en donePierde el estilode la clase task
add conserva las 2 palabras y suma otra, mientras que setAttribute reemplaza el valor por done. Para agregar una clase, usa classList.

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.

En la lista de pendientes para la incorporación de un empleado nuevo, actualiza la clase que marca las tareas terminadas. El HTML de la lista ya está definido.

① Agrega la clase done al elemento C-2.

② El elemento C-1 se marcó como terminado por error. Alterna su clase done con una sola llamada.

③ Escribe en el elemento C-2 un atributo data con la fecha en que se terminó.

④ Muestra el HTML de la lista, incluidas las etiquetas del propio ul.

Editor JavaScript / TypeScript

Ejecutar el código para ver el resultado

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
Cantidad al volver a buscar y cantidad guardada
querySelectorAll("li") otra vezBusca en el DOMtras removeLos li deT-1 y T-3length es 2tasks, obtenidoantes de removeLos mismos queal obtenerloLos li deT-1, T-2 y T-3length es 3
Si vuelves a buscar después de remove, solo aparecen T-1 y T-3, mientras que tasks sigue teniendo 3. Para revisar la cantidad, vuelve a buscar.

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.

En la pantalla para redactar un correo, quita de una vez de la lista todos los adjuntos cancelados. El HTML de la lista y su elemento ul, attachmentList, ya están definidos.

① Obtén todos los elementos de archivos cancelados y quítalos de la lista de uno en uno.

② Después de quitarlos, muestra cuántos elementos obtuviste en ①.

③ Vuelve a obtener los elementos de archivos cancelados y muestra cuántos hay.

④ Muestra el HTML de la lista, incluidas las etiquetas del propio ul.

Editor JavaScript / TypeScript

Ejecutar el código para ver el resultado

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">&lt;a href="https://example.com/login"&gt;Vuelve a iniciar sesión&lt;/a&gt; para continuar</p>
El p después de recibir la misma entrada
p tras asignar a innerHTML
Elemento a — href es example.com/login
  • Su texto es «Vuelve a iniciar sesión»
  • Al hacer clic, lleva a otro sitio
Texto — para continuar
  • Va después del elemento a
p tras asignar a textContent
Texto — toda la entrada en una sola pieza
  • <a href="…">Vuelve a iniciar sesión</a> para continuar
  • 0 elementos a
Con innerHTML, la etiqueta a se convierte en elemento; con textContent, se queda como texto. textContent es lo que evita que la entrada se convierta en elementos.

Los &lt; y &gt; 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é cambiasCómo se escribeQué tener en cuenta
El elementocreateElement / append / removeNo está en la página hasta append
ClasesclassList.add / remove / togglesetAttribute reemplaza todo el valor
Atributos data-dataset.doneAt = "9/10"Sin data-, mayúscula tras cada guion
Otros atributossetAttribute("href", "/tasks/T-2")Lo agrega o reemplaza su valor
Texto interiortextContentAquí va la entrada del usuario
HTML interiorinnerHTMLSolo 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.

Muestra una publicación en la sección de reseñas de un producto. El nombre de quien publica author, el comentario comment y el elemento de la sección reviewArea ya están declarados.

① Crea un elemento article y agrégale dentro, en este orden, un elemento strong con el nombre de quien publica y un elemento p con el comentario. Inserta la entrada como texto.

② Agrega el article a la sección y muestra cuántos elementos a hay en la página.

③ Muestra el HTML, incluidas las etiquetas del propio article.

Editor JavaScript / TypeScript

Ejecutar el código para ver el resultado
QUIZ

Verificación de conocimientos

Responde cada pregunta una a una.

Pregunta 1Justo después de const li = document.createElement("li");, ¿en qué parte de la página está li?

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?