wandres.dev
CONSOLE II · Utilidades y contexto

Las utilidades de selección: $0, $$, $x y el puente con Elements

Las funciones que la consola añade al contexto global, qué devuelve exactamente cada una, y por qué no existen fuera de la consola.

⏱ 14 min

La consola de las DevTools no evalúa JavaScript puro: evalúa JavaScript con un puñado de funciones adicionales inyectadas en el ámbito, que no existen en la página y que desaparecen en cuanto ese código se ejecuta desde un fichero. Son una docena, resuelven tareas que de otro modo cuestan varias líneas, y la mayoría de la gente conoce dos. Esta lección cubre las de selección, que son las que más se usan y las que tienen los matices más consecuentes.

🎯 Al terminar esta lección sabrás
  • Usar las variables de selección reciente y entender cuándo cambian.
  • Distinguir la función de selección de la consola de la de cualquier librería con el mismo nombre.
  • Elegir entre las tres funciones de consulta según lo que devuelvan.
  • Explicar por qué estas funciones no se pueden usar en código de la aplicación.

Las variables de selección reciente

$0 es el nodo seleccionado ahora mismo en el panel de Elements. $1 es el anterior, $2 el anterior a ese, y así hasta $4. Cada vez que seleccionas un nodo nuevo, la pila se desplaza.

Ese comportamiento tiene una implicación práctica que hay que interiorizar: si trabajas con $0 y luego seleccionas otro nodo para mirar algo, tu $0 ya no es el que era. Para trabajar de forma sostenida con un nodo concreto, la opción correcta es guardarlo como variable global desde el menú contextual, o asignarlo a una variable tuya.

const objetivo = $0;   // ahora ya puedes seleccionar lo que quieras

La pila de cinco es más útil de lo que parece. El patrón de seleccionar dos elementos consecutivamente y compararlos con $0 y $1 responde a la pregunta “por qué estos dos se ven distintos” sin ningún trabajo de identificación.

Existe además $_, que contiene el resultado de la última expresión evaluada en la consola. Permite encadenar sin guardar en variables.

$$('img');            // devuelve un array
$_.filter(i => !i.complete);   // opera sobre el resultado anterior
ℹ️
Nota

En el panel de Sources, cuando la ejecución está detenida, $0 sigue apuntando al nodo seleccionado en Elements, no a nada relacionado con el marco actual. Son dos contextos independientes y es fácil olvidarlo.

Las tres funciones de consulta

$(selector, contexto) devuelve el primer elemento que casa, igual que querySelector. El segundo argumento, opcional, es el nodo raíz donde buscar.

$$(selector, contexto) devuelve todos los elementos que casan. La diferencia importante con querySelectorAll es que devuelve un array de verdad, no una NodeList. Eso significa que se pueden encadenar filter, map, reduce, sort y todo lo demás directamente, sin el rodeo del operador de propagación.

$x(expresion, contexto) evalúa una expresión XPath y devuelve un array de nodos, o un valor primitivo si la expresión lo produce.

El segundo argumento de las tres es lo que las hace funcionar dentro de un shadow DOM abierto.

const comp = document.querySelector('mi-componente');
$$('button', comp.shadowRoot);

El conflicto con jQuery

El matiz que produce más confusión. Si la página carga jQuery o cualquier librería que defina $ en el objeto global, la definición de la página gana y $ en la consola pasa a ser la de la librería. Las DevTools no la sobrescriben, precisamente para no romper la depuración de esas páginas.

$$ y $x no suelen entrar en conflicto y siguen siendo las de la consola.

La forma de saber cuál tienes es preguntar.

console.log($.toString().slice(0, 80));

Si sale algo con aspecto de código nativo, es la de la consola. Si sale código de jQuery, es la de la librería, y $('div') te devolverá un objeto de jQuery en vez de un elemento.

Por qué no existen fuera de la consola

Estas funciones se inyectan en el ámbito de evaluación de la consola, no en el objeto global de la página. Un console.log($$) desde la consola funciona; el mismo código en un fichero de tu aplicación lanza un ReferenceError.

La consecuencia práctica es que el código que escribes en la consola no se puede copiar y pegar en un fichero sin traducirlo, y esa traducción tiene tres sustituciones.

En la consola En un fichero
$(sel) document.querySelector(sel)
$$(sel) [...document.querySelectorAll(sel)]
$x(expr) Evaluación explícita con document.evaluate
$0 Una referencia obtenida de otra forma

La tercera es la más incómoda, porque la API estándar de XPath es verbosa. Merece la pena tener el equivalente a mano si vas a moverlo a código.

function xpath(expresion, raiz = document) {
  const resultado = document.evaluate(expresion, raiz, null, XPathResult.ORDERED_NODE_SNAPSHOT_TYPE, null);
  return Array.from({ length: resultado.snapshotLength }, (_, i) => resultado.snapshotItem(i));
}

Los snippets, que se ejecutan en el contexto de la consola, tienen acceso a las utilidades. Es una de las razones por las que un snippet es mejor sitio que un fichero para guardar herramientas de diagnóstico.

Que $$ devuelva un array de verdad no es un detalle, es lo que hace posible el diagnóstico en una línea

La diferencia entre una NodeList y un array parece cosmética y es la que decide si una herramienta de diagnóstico cabe en una línea o necesita cinco. Una NodeList tiene forEach y poco más: no tiene filter, ni map, ni sort, ni reduce, ni some, ni find. Eso obliga a envolverla en un operador de propagación antes de hacer nada interesante, y ese pequeño peaje es suficiente para que la mayoría de la gente no escriba la consulta que resolvería su problema y siga mirando elementos de uno en uno en el panel. Con $$ devolviendo un array, la barrera desaparece, y con ella se abre una forma de trabajar que vale la pena adoptar conscientemente: cualquier pregunta sobre la página que empiece por “cuántos”, “cuáles” o “hay alguno que” se responde con una expresión, no mirando. Cuántos elementos tienen position: fixed. Cuáles son los que desbordan horizontalmente. Hay algún botón sin nombre accesible. Cuál es la imagen más grande que se está descargando. Cada una de esas es una línea con $$ seguida de filter y map, y cada una sustituye entre cinco y veinte minutos de inspección manual con un resultado que además es exhaustivo, mientras que la inspección manual siempre se deja algo. La consecuencia es que la consola deja de ser un visor de logs y pasa a ser el lenguaje de consulta de la página, que es lo que de verdad es. El nivel de snippets convierte esas consultas en herramientas guardadas, pero el cambio de mentalidad ocurre aquí, en el momento en que dejas de buscar el elemento y empiezas a preguntar por el conjunto.