Saltar al contenido
ambito.js · devschool

Ámbito y closures en JavaScript

Lección 11 de 38 · 5 min de lectura · Actualizado el

En esta lección
  1. Tres ámbitos
  2. Las variables globales son peligrosas
  3. var, let y const
  4. Hoisting: la “elevación”
  5. Closures
  6. IIFE: un patrón antiguo

El ámbito (scope) de una variable es la zona del código donde existe y se puede usar. Entenderlo evita dos de los errores más frecuentes: usar una variable que “no existe” y modificar sin querer una variable de otra parte del programa.

Tres ámbitos

Global. Una variable declarada fuera de cualquier función o bloque es global: se puede usar desde cualquier parte del archivo.

let visitas = 0;              // global

function registrarVisita() {
  visitas++;                  // la función puede leerla y cambiarla
}

De función. Una variable declarada dentro de una función solo existe dentro de ella:

function saludar() {
  const mensaje = "Hola";
  console.log(mensaje);
}
saludar();
console.log(mensaje);  // ReferenceError: mensaje is not defined

De bloque. Con let y const, una variable declarada dentro de unas llaves { } (un if, un for…) solo existe dentro de esas llaves:

if (true) {
  const temporal = 42;
}
console.log(temporal);  // ReferenceError

Las variables de dentro pueden ver las de fuera, pero no al revés. Si hay una variable con el mismo nombre dentro y fuera, dentro se usa la de dentro (la de fuera queda “tapada”).

Las variables globales son peligrosas

En el navegador, todos los scripts de una misma página comparten las variables globales, aunque estén en archivos distintos. Si dos archivos declaran una variable global total, se pisan entre sí. Y cualquier función puede cambiar una variable global, lo que hace muy difícil saber quién ha cambiado un valor cuando algo falla.

Algunas metodologías prohíben las variables globales; las demás piden reducirlas al mínimo. Dos consejos:

  • Si varias funciones necesitan compartir datos, agrúpalos en un único objeto global en lugar de tener muchas variables sueltas: const estado = { puntos: 0, vidas: 3 };.
  • Usa módulos: las variables de un módulo no son globales.

Un error típico al empezar es el contrario: declarar dentro de una función una variable que debería ser compartida. Si un contador “se reinicia” cada vez que pulsas un botón, probablemente esté declarado dentro de la función del botón.

var, let y const

varletconst
ÁmbitoDe funciónDe bloqueDe bloque
Se puede reasignarSíSíNo
Se puede declarar dos vecesSí (!)NoNo
Se puede usar antes de declararlaSí, vale undefined (!)No, da errorNo, da error

var es la forma antigua y tiene comportamientos traicioneros:

function ejemplo() {
  var x = 1;
  if (true) {
    var x = 2;        // ¡es la MISMA variable! var ignora el bloque
  }
  console.log(x);     // 2
}

function ejemploLet() {
  let x = 1;
  if (true) {
    let x = 2;        // variable distinta, solo del bloque
  }
  console.log(x);     // 1
}

Usa const por defecto y let cuando necesites reasignar. No uses var salvo para mantener código antiguo.

Hoisting: la “elevación”

Antes de ejecutar el código, JavaScript recorre cada ámbito y registra todas las declaraciones. Es como si las subiera al principio. Por eso se llama hoisting (elevación).

Con las funciones declaradas con function es útil: puedes llamarlas antes de escribirlas.

saludar();                 // funciona
function saludar() {
  console.log("Hola");
}

Esto permite poner el código principal arriba y las funciones de ayuda abajo.

Con var es confuso: la variable existe desde el principio, pero vale undefined hasta la línea donde se le da valor.

console.log(nombre);  // undefined (no da error, pero tampoco lo que querías)
var nombre = "Ana";

Con let y const es seguro: la variable también se registra, pero usarla antes de su declaración da un error claro. Esa zona anterior a la declaración se llama zona muerta temporal.

console.log(edad);    // ReferenceError: Cannot access 'edad' before initialization
let edad = 30;

Las funciones flecha guardadas en una const siguen las reglas de const: no se pueden usar antes de su línea.

Closures

Una función recuerda las variables del sitio donde se creó, aunque ese sitio ya haya terminado de ejecutarse. Esa combinación de función más variables recordadas se llama closure (cierre).

function crearSaludo(saludo) {
  return function (nombre) {
    return `${saludo}, ${nombre}`;
  };
}

const saludoFormal = crearSaludo("Buenos días");
const saludoColega = crearSaludo("¿Qué pasa");

saludoFormal("Sra. López");  // "Buenos días, Sra. López"
saludoColega("Luis");        // "¿Qué pasa, Luis"

crearSaludo terminó hace rato, pero cada función que devolvió sigue recordando su propio saludo.

Para qué sirven:

  • Datos privados: en el contador del ejemplo de abajo, nadie puede cambiar cuenta desde fuera; solo la función que se devolvió.
  • Funciones configurables, como crearSaludo.
  • Callbacks que necesitan recordar datos del momento en que se crearon (manejadores de eventos, temporizadores).

Sin darte cuenta, cada vez que un addEventListener usa una variable de fuera, estás usando un closure.

IIFE: un patrón antiguo

En código anterior a 2015 verás esto:

(function () {
  var privada = 1;
  // ...
})();

Es una función que se crea y se ejecuta al momento (Immediately Invoked Function Expression). Se usaba para crear un ámbito propio y no llenar el ámbito global de variables. Hoy, con let, const, bloques y módulos, ya no hace falta, pero conviene reconocerla.

Pruébalo tú

Cambia el código y pulsa Ejecutar (o Ctrl + Enter).

ambito.js
Consola

Pon a prueba lo que has aprendido

[JavaScript] ¿Qué muestra la consola?
let x = 1;
if (true) {
  let x = 2;
}
console.log(x);

[JavaScript] ¿Qué ocurre al ejecutar esto?
console.log(edad);
let edad = 30;

¿Te ha quedado claro? Márcala y verás tu progreso en el explorador.