Diferencia entre getElementsBy* y querySelectorAll

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();