Ir al contenido principal

Ámbito global y local

Introducción

El ámbito (o scope en inglés) es uno de los conceptos fundamentales en JavaScript, ya que determina la visibilidad y accesibilidad de las variables en diferentes partes de nuestro código. Entender correctamente cómo funcionan los diferentes tipos de ámbitos nos ayudará a evitar errores comunes, escribir código más limpio y predecible, y comprender en profundidad el funcionamiento del lenguaje. En este artículo, exploraremos los diferentes tipos de ámbitos en JavaScript, cómo se crean y anidan, y las mejores prácticas para trabajar con ellos.

Concepto de ámbito (scope) en JavaScript

El ámbito en JavaScript define la zona del programa donde una variable está disponible. En otras palabras, determina desde qué partes del código podemos acceder a una variable específica y en qué partes esa variable es invisible o inaccesible.

Imagina el ámbito como una serie de habitaciones conectadas. Desde una habitación, puedes ver lo que hay dentro de ella y también lo que hay en habitaciones más grandes que la contienen, pero no puedes ver lo que hay en habitaciones más pequeñas dentro de otras habitaciones.

JavaScript tiene varios tipos de ámbitos:

  1. Ámbito global
  2. Ámbito de función (local)
  3. Ámbito de bloque (introducido en ES6 con let y const)
  4. Ámbito de módulo (para código organizado en módulos ES6)

Centrémonos primero en los dos principales: el ámbito global y el ámbito local.

Ámbito global vs. ámbito local

Ámbito global

El ámbito global es el nivel más externo en un programa JavaScript. Las variables declaradas en este ámbito son variables globales y tienen las siguientes características:

  • Están disponibles en todo el programa, dentro y fuera de cualquier función o bloque.
  • Se crean cuando se declaran fuera de cualquier función o bloque.
  • Se convierten en propiedades del objeto global (window en navegadores, global en Node.js).
  • Existen durante todo el tiempo de ejecución del programa.

Ejemplo de variables globales:

// Variables globales
const sitioWeb = "miportal.com";
let contador = 0;
var esActivo = true;

function mostrarSitio() {
  // Podemos acceder a las variables globales desde dentro de una función
  console.log(`El sitio web es ${sitioWeb}`);
  contador++; // Modificamos la variable global
}

mostrarSitio(); // El sitio web es miportal.com
console.log(contador); // 1
Declaración implícita de variables globales

En JavaScript, si asignamos un valor a una variable sin declararla previamente con var, let o const (y no estamos en modo estricto), se crea implícitamente una variable global:

function incrementarContador() {
  contadorImplicito = 1; // Variable global creada implícitamente
}

incrementarContador();
console.log(contadorImplicito); // 1

Esta es una fuente común de errores y debe evitarse utilizando modo estricto ('use strict') y declarando siempre las variables con var, let o const:

'use strict';

function incrementarContador() {
  contadorImplicito = 1; // Error: contadorImplicito no está definido
}

Ámbito local

El ámbito local se crea dentro de funciones y bloques. Las variables declaradas en un ámbito local son variables locales y tienen las siguientes características:

  • Solo están disponibles dentro de la función o bloque donde se declaran.
  • Se crean cuando se ejecuta la función o bloque y se destruyen cuando la ejecución termina.
  • No afectan a variables con el mismo nombre en otros ámbitos.
Ámbito de función

Las variables declaradas dentro de una función solo son accesibles dentro de esa función y sus funciones anidadas:

function calcularTotal() {
  // Variables locales a la función
  const precio = 100;
  const impuesto = 0.21;
  const total = precio * (1 + impuesto);
  
  console.log(`Total: ${total}€`);
}

calcularTotal(); // Total: 121€
console.log(precio); // Error: precio is not defined

Las variables locales "ocultan" a las variables globales con el mismo nombre:

const mensaje = "Global";

function mostrarMensaje() {
  const mensaje = "Local";
  console.log(mensaje); // Usa la variable local
}

mostrarMensaje(); // Local
console.log(mensaje); // Global

Ámbito de bloque (let, const)

Con la introducción de let y const en ES6, JavaScript ganó el concepto de ámbito de bloque. Un bloque se define por llaves {}, como en declaraciones if, bucles for o simplemente un bloque independiente.

Las variables declaradas con let y const respetan el ámbito de bloque, mientras que var solo respeta el ámbito de función:

Ejemplo con let y const (ámbito de bloque)

function ejemploAmbitos() {
  if (true) {
    let variableLet = "Solo visible en este bloque";
    const variableConst = "También solo en este bloque";
    var variableVar = "Visible en toda la función";
    
    console.log(variableLet); // Accesible aquí
  }
  
  console.log(variableVar); // Accesible aquí
  console.log(variableLet); // Error: variableLet is not defined
}

Ejemplo con bucles

for (let i = 0; i < 3; i++) {
  console.log(`Dentro del bucle: ${i}`);
}
console.log(i); // Error: i is not defined

// Comparado con var:
for (var j = 0; j < 3; j++) {
  console.log(`Dentro del bucle: ${j}`);
}
console.log(j); // 3 (j es accesible fuera del bucle)

Anidación de ámbitos

Los ámbitos en JavaScript pueden anidarse, creando una jerarquía. Una función dentro de otra función tiene acceso a las variables de su propia función, de la función contenedora y del ámbito global:

const global = "Variable global";

function externa() {
  const externa = "Variable de función externa";
  
  function interna() {
    const interna = "Variable de función interna";
    
    console.log(global);    // Accesible (ámbito global)
    console.log(externa);   // Accesible (ámbito de función contenedora)
    console.log(interna);   // Accesible (ámbito local)
  }
  
  interna();
  console.log(interna); // Error: interna is not defined
}

externa();
console.log(externa); // Error: externa is not defined

Esta estructura anidada de ámbitos se conoce como cadena de ámbitos o scope chain. Cuando intentamos acceder a una variable, JavaScript primero busca en el ámbito actual. Si no la encuentra, sube al ámbito contenedor, y así sucesivamente hasta llegar al ámbito global. Si la variable no se encuentra en ningún lugar, se produce un error.

Visualización de la cadena de ámbitos

Ámbito Global
│
├── sitioWeb, contador, esActivo
│
└── Ámbito de función externa()
    │
    ├── externa
    │
    └── Ámbito de función interna()
        │
        └── interna

Acceso a variables desde diferentes ámbitos

Como ya hemos visto, el acceso a variables sigue reglas específicas basadas en la estructura de ámbitos:

  1. Las funciones pueden acceder a variables declaradas en su propio ámbito.
  2. Las funciones pueden acceder a variables declaradas en ámbitos contenedores.
  3. Las funciones no pueden acceder a variables declaradas en funciones internas.

Ejemplo completo de acceso a variables

const nivelGlobal = "Accesible en todas partes";

function primerNivel() {
  const nivelUno = "Accesible en primerNivel y sus funciones internas";
  
  function segundoNivel() {
    const nivelDos = "Accesible solo en segundoNivel y sus funciones internas";
    
    console.log(nivelGlobal); // ✓ Accesible
    console.log(nivelUno);    // ✓ Accesible
    console.log(nivelDos);    // ✓ Accesible
  }
  
  segundoNivel();
  
  console.log(nivelGlobal); // ✓ Accesible
  console.log(nivelUno);    // ✓ Accesible
  console.log(nivelDos);    // ✗ Error: nivelDos is not defined
}

primerNivel();

console.log(nivelGlobal); // ✓ Accesible
console.log(nivelUno);    // ✗ Error: nivelUno is not defined
console.log(nivelDos);    // ✗ Error: nivelDos is not defined

Sombra de variables (Variable Shadowing)

Cuando declaramos una variable con el mismo nombre que una variable de un ámbito externo, la variable interna "oculta" o hace "sombra" a la externa:

const color = "rojo";

function mostrarColor() {
  const color = "azul"; // Esta variable hace sombra a la global
  console.log(color); // azul
  
  if (true) {
    const color = "verde"; // Esta variable hace sombra a la de la función
    console.log(color); // verde
  }
  
  console.log(color); // azul (de nuevo)
}

mostrarColor();
console.log(color); // rojo

Ventajas del ámbito local

Usar ámbitos locales adecuadamente proporciona varias ventajas:

  1. Evita colisiones de nombres: Las variables con el mismo nombre pueden existir en diferentes ámbitos sin interferir entre sí.
  2. Mejora la legibilidad: Las variables declaradas cerca de donde se usan hacen el código más fácil de entender.
  3. Reduce la complejidad: Limitar el ámbito de las variables reduce la complejidad mental necesaria para entender el código.
  4. Facilita el mantenimiento: El código es más modular y más fácil de mantener cuando las variables tienen ámbitos bien definidos.
  5. Mejora la gestión de memoria: Las variables locales se liberan cuando su ámbito termina de ejecutarse.

Problemas comunes y buenas prácticas

1. Minimizar variables globales

Las variables globales pueden causar problemas difíciles de rastrear, como:

  • Colisiones de nombres entre diferentes partes del código
  • Dependencias ocultas
  • Dificultad para probar y mantener el código

Recomendación: Minimiza el uso de variables globales. Si necesitas compartir datos entre diferentes partes del código, considera usar patrones como módulos o clases.

// No recomendado: Variables globales
let usuarioActual = null;
let configuracion = {};

// Mejor enfoque: Módulo o namespace
const AppEstado = (function() {
  let usuarioActual = null;
  let configuracion = {};
  
  return {
    getUsuario: function() { return usuarioActual; },
    setUsuario: function(usuario) { usuarioActual = usuario; },
    getConfiguracion: function() { return configuracion; },
    setConfiguracion: function(config) { configuracion = config; }
  };
})();

2. Usar let y const en lugar de var

Las declaraciones let y const proporcionan ámbito de bloque, lo que reduce la posibilidad de errores:

// Problema con var
function ejemploVar() {
  var i = 10;
  if (true) {
    var i = 20; // Sobrescribe la variable anterior
    console.log(i); // 20
  }
  console.log(i); // 20 - ¡La variable fue modificada!
}

// Solución con let
function ejemploLet() {
  let i = 10;
  if (true) {
    let i = 20; // Nueva variable solo para este bloque
    console.log(i); // 20
  }
  console.log(i); // 10 - Mantiene su valor original
}

3. Preferir const cuando el valor no cambia

Utiliza const para declarar variables que no necesitan ser reasignadas, lo que hace el código más predecible:

// Recomendado
const API_URL = "https://api.ejemplo.com";
const MAX_INTENTOS = 3;

// Para objetos y arrays que no necesitan ser reasignados
const configuracion = {
  tema: "claro",
  idioma: "es"
};

// Podemos modificar sus propiedades
configuracion.tema = "oscuro"; // Esto es válido

// Pero no podemos reasignar el objeto
configuracion = {}; // Error: Assignment to constant variable

4. Declarar variables al principio de su ámbito

Declara las variables al principio de su ámbito para mejorar la legibilidad y evitar confusiones con el hoisting (elevación):

function procesarDatos(datos) {
  // Declaraciones al principio
  const longitud = datos.length;
  let resultado = 0;
  let i;
  
  // Lógica después
  for (i = 0; i < longitud; i++) {
    resultado += datos[i];
  }
  
  return resultado;
}

5. Usar funciones auto-invocadas para crear ámbitos aislados

Las funciones auto-invocadas (IIFE) son útiles para crear ámbitos aislados que no contaminan el espacio global:

// Todo en el ámbito global
const nombre = "Juan";
const apellido = "García";
const edad = 30;

// Mejor enfoque: IIFE para encapsular variables
(function() {
  const nombre = "Juan";
  const apellido = "García";
  const edad = 30;
  
  console.log(`${nombre} ${apellido}, ${edad} años`);
})();

// Las variables no están disponibles fuera
console.log(typeof nombre); // "undefined"

Resumen

El concepto de ámbito en JavaScript determina la visibilidad y accesibilidad de las variables en diferentes partes del código. Hemos explorado los diferentes tipos de ámbitos: global, de función (local) y de bloque, así como sus características y comportamientos.

El ámbito global contiene variables accesibles desde cualquier parte del programa, mientras que los ámbitos locales (de función y de bloque) limitan la visibilidad de las variables a ciertas secciones de código. La anidación de ámbitos crea una cadena de ámbitos que JavaScript recorre al buscar una variable.

Seguir buenas prácticas como minimizar el uso de variables globales, preferir let y const sobre var, y declarar variables en el ámbito más reducido posible, nos ayudará a escribir código más claro, mantenible y con menos errores.

En el próximo artículo, profundizaremos en el concepto de hoisting (elevación), un comportamiento de JavaScript relacionado con los ámbitos que afecta a cómo se procesan las declaraciones de variables y funciones durante la ejecución del código.