Scope y lexical scope: dónde viven tus variables
El scope determina qué variables son visibles desde dónde. JavaScript usa lexical scope — el scope se fija cuando escribes el código, no cuando se ejecuta.
Tipos de scope
JavaScript tiene tres niveles de scope que determinan dónde vive cada variable:
// 1. Scope global — visible desde cualquier lugar
var appNombre = "MiApp"; // var en global scope
let version = "1.0.0"; // let en global scope
function mostrarInfo() {
// 2. Scope de función — solo visible dentro de la función
var mensajeInterno = "Este mensaje es privado";
console.log(appNombre); // ✅ accede al global
console.log(mensajeInterno); // ✅ local
if (true) {
// 3. Scope de bloque — solo dentro de {}
let bloque = "solo en este bloque";
const constBloque = "también aquí";
var fueraDelBloque = "var ignora el bloque"; // ❌ no respeta bloque
console.log(bloque); // ✅
console.log(constBloque); // ✅
}
// console.log(bloque); // ❌ ReferenceError
console.log(fueraDelBloque); // ✅ var escapó del bloque
}
mostrarInfo();// TypeScript refuerza el block scope con análisis estático
const appNombre: string = "MiApp";
const version: string = "1.0.0";
function mostrarInfo(): void {
const mensajeInterno: string = "Este mensaje es privado";
console.log(appNombre);
console.log(mensajeInterno);
if (true) {
let bloque: string = "solo en este bloque";
const constBloque: string = "también aquí";
console.log(bloque);
console.log(constBloque);
}
// TypeScript marca error en compilación si intentas usar
// 'bloque' fuera del if — no necesitas el runtime
}MiApp
Este mensaje es privado
solo en este bloque
también aquí
undefinedLexical scope — el scope se determina al escribir
Lexical scope (o static scope) significa que el scope de una variable se determina por dónde está escrita en el código, no por dónde se llama la función.
const saludo = "Hola";
function externo() {
const nombre = "Ana";
function interno() {
// interno "ve" las variables de externo y del global
// porque fue DEFINIDO dentro de externo — no importa
// desde dónde se llame
console.log(`${saludo}, ${nombre}!`); // "Hola, Ana!"
}
interno();
}
externo();
// El scope léxico es estático:
function crearSaludo() {
const base = "Buenos días";
return function(persona) {
// Esta función "recuerda" base porque fue definida aquí
return `${base}, ${persona}`;
};
}
const saludar = crearSaludo();
console.log(saludar("Carlos")); // "Buenos días, Carlos"
// base no existe aquí, pero la función lo recuerdaconst saludo: string = "Hola";
function externo(): void {
const nombre: string = "Ana";
function interno(): void {
console.log(`${saludo}, ${nombre}!`);
}
interno();
}
externo();
function crearSaludo(): (persona: string) => string {
const base: string = "Buenos días";
return function(persona: string): string {
return `${base}, ${persona}`;
};
}
const saludar = crearSaludo();
console.log(saludar("Carlos")); // "Buenos días, Carlos"Hola, Ana!
Buenos días, CarlosJavaScript usa lexical scope — el scope se fija donde defines la función. Otros lenguajes como Bash usan dynamic scope — donde se llama la función. Esta diferencia explica por qué los closures funcionan en JS.
Scope chain — búsqueda hacia arriba
Cuando JavaScript busca una variable, sube por la cadena de scopes hasta encontrarla o llegar al global.
const nivel = "global";
function primerNivel() {
const nivel = "función"; // sombrea al global
function segundoNivel() {
// No tiene 'nivel' propio — busca en primerNivel
function tercerNivel() {
// No tiene 'nivel' — sube a segundoNivel — no tiene —
// sube a primerNivel — ¡encontrado!
console.log(nivel); // "función"
}
tercerNivel();
}
segundoNivel();
}
primerNivel();
// Shadowing — una variable más interna oculta a la externa
function demostrarShadowing() {
let x = 10;
if (true) {
let x = 20; // nueva x en scope de bloque
console.log(x); // 20 — la del bloque
}
console.log(x); // 10 — la del scope de función
}
demostrarShadowing();const nivel: string = "global";
function primerNivel(): void {
const nivel: string = "función";
function segundoNivel(): void {
function tercerNivel(): void {
console.log(nivel); // "función" — sube la cadena
}
tercerNivel();
}
segundoNivel();
}
primerNivel();
function demostrarShadowing(): void {
let x: number = 10;
if (true) {
let x: number = 20; // TypeScript permite shadowing
console.log(x); // 20
}
console.log(x); // 10
}
demostrarShadowing();función
20
10IIFE — el patrón clásico de scope aislado
El IIFE (Immediately Invoked Function Expression) crea un scope privado de forma inmediata. Era el patrón estándar para encapsulación antes de los módulos ES6.
// Sintaxis básica del IIFE
(function() {
const privado = "nadie me ve desde fuera";
console.log(`Dentro del IIFE: ${privado}`);
})();
// console.log(privado); // ❌ ReferenceError
// IIFE con parámetros
(function(nombre, version) {
console.log(`${nombre} v${version} iniciado`);
})("MiApp", "2.0");
// Arrow function IIFE
const resultado = (() => {
const a = 10;
const b = 20;
return a + b;
})();
console.log(`Resultado: ${resultado}`); // 30
// Hoy usamos módulos ES6 en lugar de IIFE:
// import/export maneja el scope automáticamente// IIFE en TypeScript
(function(): void {
const privado: string = "nadie me ve desde fuera";
console.log(`Dentro del IIFE: ${privado}`);
})();
// IIFE con parámetros tipados
(function(nombre: string, version: string): void {
console.log(`${nombre} v${version} iniciado`);
})("MiApp", "2.0");
// Arrow IIFE que retorna un valor
const resultado: number = ((): number => {
const a: number = 10;
const b: number = 20;
return a + b;
})();
console.log(`Resultado: ${resultado}`); // 30Dentro del IIFE: nadie me ve desde fuera
MiApp v2.0 iniciado
Resultado: 30En código moderno con ES modules, raramente necesitas un IIFE — cada archivo ya tiene su propio scope. Pero los verás en código legacy, bundles minificados y patrones como el Module Pattern.