Ámbito y closures en JavaScript
En esta lección
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
var | let | const | |
|---|---|---|---|
| Ámbito | De función | De bloque | De bloque |
| Se puede reasignar | Sí | Sí | No |
| Se puede declarar dos veces | Sí (!) | No | No |
| Se puede usar antes de declararla | Sí, vale undefined (!) | No, da error | No, 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
cuentadesde 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).
Pon a prueba lo que has aprendido
¿Te ha quedado claro? Márcala y verás tu progreso en el explorador.