Alcance y Closures en JavaScript
Alcance (Scope)
La palabra reservada var permite declraar variables múltiples veces. Al declarar una variable existente, JavaScript omite la declaración y continúa con la asignación.
En modo no estricto, si se asigna un valor a una variable sin declararla (a = 2), JavaScript crea automáticamente una variable global llamada a. En modo estricto, esto genera un error y se prohíbe la creación implícita de variables globales.
function procesar(valor) {
console.log(valor + resultado);
resultado = valor;
}
procesar(2); // ReferenceError: resultado is not defined, resultado no está en el alcance actual
function procesar(valor) {
console.log(valor + resultado);
let resultado = valor;
}
procesar(2); // ReferenceError: Cannot access 'resultado' before initialization, let no permite elevación de variables
function procesar(valor) {
console.log(valor + resultado);
var resultado = valor;
}
procesar(2); // NaN, var permite elevación, resultado está declarado pero sin valor, devuelve undefined
function procesar(valor) {
var resultado = valor;
}
procesar(2);
console.log(resultado); // ReferenceError: resultado is not defined, la variable var solo es accesible dentro de la función
function procesar(valor) {
resultado = valor;
}
procesar(2);
console.log(resultado); // 2, resultado es una variable global
function procesar(valor) {
resultado = valor;
}
var resultado = 3;
procesar(2);
console.log(resultado); // 2, la función modificó la variable global
Las variables declaradas con let y const no se añaden al objeto window, a diferencia de las declaradas con var:
valorGlobal = 1;
var varGlobal = 2;
let letGlobal = 3;
console.log(window.valorGlobal, window.varGlobal, window.letGlobal); // 1,2,undefined
El alcance de bloque permite que las variables sean destruidas cuando ya no son necesarias:
function procesar(datos) {
// Realizar operaciones con los datos
}
// someReallyBigData se destruirá automáticamente después de este bloque
{
let someReallyBigData = { ... };
procesar(someReallyBigData);
}
La elevación (hoisting) afecta tanto a variables como a funciones, pero con diferentes comportamientos:
ejecutarFuncion();
function ejecutarFuncion() {
console.log(variable); // undefined
var variable = 2;
}
console.log(variable); // ReferenceError: variable is not defined
ejecutarFunc(); // TypeError, la función existe pero es undefined
var ejecutarFunc = function() {
// ...
};
Closures
Un closure ocurre cuando una función mantiene una referencia a su ámbito léxico original, incluso cuando esa función se ejecuta fuera de ese ámbito.
function crearCalculadora() {
let contador = 0;
function incrementar() {
contador++;
console.log(contador);
}
return incrementar;
}
const miCalculadora = crearCalculadora();
miCalculadora(); // 1
miCalculadora(); // 2
Los closures son útiles para manejar operaciones asíncronas como setTimeout:
// Problema: todos los timers muestran el mismo valor final
for (var i = 1; i <= 5; i++) {
setTimeout(function() {
console.log(i);
}, i * 1000);
}
// Solución 1: Usar una función autoejecutable para capturar el valor actual
for (var i = 1; i <= 5; i++) {
(function(valorActual) {
setTimeout(function() {
console.log(valorActual);
}, valorActual * 1000);
})(i);
}
// Solución 2: Usar let que crea un nuevo binding por cada iteración
for (let i = 1; i <= 5; i++) {
setTimeout(function() {
console.log(i);
}, i * 1000);
}
Los closures también pueden utilizarse para crear encapsulación y proteger datos:
function crearContadorPrivado() {
let contador = 0;
return {
incrementar: function() {
return ++contador;
},
decrementar: function() {
return --contador;
},
obtenerValor: function() {
return contador;
}
};
}
const miContador = crearContadorPrivado();
console.log(miContador.incrementar()); // 1
console.log(miContador.incrementar()); // 2
console.log(miContador.obtenerValor()); // 2
// No hay forma directa de acceder o modificar contador desde fuera