De entrada puede parecer que ambos métodos getElementsBy*
y querySelectorAll
sean exactamente idénticos pero no es así.
La diferencia es que querySelectorAll
devuelve un listado de nodos y getElementsBy*
devuelve una colección HTML.
Vamos a ver la principal diferencia entre un listado de nodos y una colección HTML para entender bien cuando conviene usar un método u otro.
NodeList
Un NodeList
es un objeto que representa un lista de nodos del DOM. Este objeto, también llamado colección (collection), es de tipo estático (static). Eso significa que su contenido no se verá alterado por futuras modificaciones en el DOM. Vamos a verlo con un ejemplo:
Supongamos que tenemos una simple lista en HTML:
<ul>
<li>0</li>
<li>1</li>
<li>2</li>
<li>3</li>
</ul>
Con querySelectorAll
obtenemos todos los elementos li
y mostramos el resultado en la consola.
let lies = document.querySelectorAll('li');
console.log(lies); // devuelve NodeList(4)
Si nos fijamos en el detalle, el objeto NodeList
que devuelve tiene los siguientes atributos:
0: li
1: li
2: li
3: li
length: 4
__proto__: NodeList
Bien, vamos a ver qué pasa si lo usamos como iterador para eliminar los elementos del DOM.
for ( let li of lies ) li.remove();
console.log(lies);
El resultado en el HTML es el siguiente:
<ul>
</ul>
Y en la consola sigue mostrando el NodeList
tal y como estaba al principio:
0: li
1: li
2: li
3: li
length: 4
__proto__: NodeList
Puede que este fuera el resultado que esperabas pero vamos a ver ahora qué pasa si utilizamos el HTMLCollection
que devuelve getElementsByTagName
.
HTMLCollection
Al igual que con el NodeList
, un HTMLCollection
devuelve una lista de nodos. La diferencia es que esta lista no es estática sino que está viva (live). Lo que hace en realidad es un mapeado de los elementos del DOM de forma que la lista muta al mismo tiempo que lo hace el DOM. Vamos a verlo claro siguiendo nuestro ejemplo.
Vamos a recuperar nuestra lista en HTML:
<ul>
<li>0</li>
<li>1</li>
<li>2</li>
<li>3</li>
</ul>
Esta vez obtenemos el listado de nodos con getElementsByTagName
y de nuevo mostramos el resultado en la consola.
let lies = document.getElementsByTagName('li')
console.log(lies); // devuelve HTMLCollection(4)
Si nos fijamos en el detalle, el objeto HTMLCollection
parece que devuelve los mismos atributos que el NodeList
:
0: li
1: li
2: li
3: li
length: 4
__proto__: HTMLCollection
Vamos ahora a ver qué pasa si lo usamos como iterador para eliminar los elementos del DOM.
for ( let li of lies ) li.remove();
console.log(lies);
Ojo por que el resultado ahora en el HTML es el siguiente:
<ul>
<li>1</li>
<li>3</li>
</ul>
Y en la consola el valor de lies
ya no es el mismo que antes:
0: li
1: li
length: 2
__proto__: HTMLCollection
¿Qué ha pasado aquí? ¿Por qué solamente ha eliminado los elementos 0 y 2?
Tal y como he dicho antes, una colección HTML es un listado de elementos vivo que muta al mismo tiempo que el DOM.
Veamos la iteración paso a paso para ver qué ha pasado:
En la primera iteración del bucle for
el primer li
ha sido eliminado del DOM. Al mismo tiempo el valor de lies
ha cambiado y ha pasado de ser un listado de 4 elementos a un listado de 3 elementos.
<li>0</li> // Este elemento desaparece y el resto se mantienen
<li>1</li>
<li>2</li>
<li>3</li>
Ahora el valor de lies
es el siguiente listado:
<li>1</li>
<li>2</li>
<li>3</li>
Sin embargo ahora es el turno de la segunda iteración del bucle for
y por eso elimina el segundo elemento de lies
.
<li>1</li>
<li>2</li> // Ahora este es el segundo elemento y por lo tanto el que se elimina
<li>3</li>
Ahora el valor de lies
es el siguiente listado:
<li>1</li>
<li>3</li>
Al llegar a la tercera iteración del bucle for
resulta que lies
ya no tiene un tercer elemento así que la ejecución se detiene dando el resultado que hemos visto antes.
Conclusión
Ahora que conoces cómo actúa cada método te será más sencillo saber cuando aplicar cada uno. Cuando quieras obtener un listado de elementos actualizado usa getElementsBy*
y cuando quieras obtener una «fotografía» del estado actual de una lista de elementos usa querySelectorAll
.
Bonus Track
También existen otras maneras de iterar sobre una colección HTML sin que se salte elementos.
While loop
Una forma sería usando un bucle while
que a diferencia del for
se ejecuta mientras existan elementos.
while ( lies[0] ) lies[0].parentNode.removeChild(lies[0]);
Reverse loop
Y otra forma es usando un bucle invertido que consiste en iterar del último elemento al primero. De esta forma nunca se saltará ningún elemento de la lista.
for ( let i = lies.length - 1; i >= 0; i-- ) lies[i].remove();