var, let y const en JavaScript: diferencias reales y cuándo usar cada uno

Antes de ES2015, var era la única forma de declarar variables en JavaScript. La llegada de let y const no fue un capricho estético: resolvió problemas reales de comportamiento que llevaban años causando bugs difíciles de rastrear. Entender las diferencias es imprescindible para escribir código predecible.

Scope: la diferencia más importante

var tiene scope de función: vive desde el principio de la función que la contiene hasta el final, sin importar dónde esté declarada. let y const tienen scope de bloque: solo existen dentro del bloque { } donde se declaran.

function ejemploVar() {
  if (true) {
    var x = 10;  // scope de función
  }
  console.log(x);  // 10 — ¡x existe fuera del if!
}

function ejemploLet() {
  if (true) {
    let y = 10;  // scope de bloque
  }
  console.log(y);  // ReferenceError: y is not defined
}

El bug clásico del bucle con var

El scope de función de var rompe los closures dentro de bucles. Este es posiblemente el bug más visto en entrevistas técnicas de JavaScript:

// Con var: todos los callbacks imprimen 3
for (var i = 0; i < 3; i++) {
  setTimeout(() => console.log(i), 100);
}
// Imprime: 3, 3, 3

// Con let: cada iteración tiene su propio i
for (let j = 0; j < 3; j++) {
  setTimeout(() => console.log(j), 100);
}
// Imprime: 0, 1, 2

Con var, todas las funciones anónimas comparten la misma variable i, que vale 3 cuando los timeouts se ejecutan. Con let, cada iteración del bucle crea un nuevo binding, por lo que cada closure captura su propio valor.

const no significa inmutable

const garantiza que la referencia no cambia, no el valor al que apunta. Con tipos primitivos es efectivamente una constante. Con objetos y arrays, el contenido sigue siendo mutable:

const PI = 3.14159;
PI = 3;  // TypeError: Assignment to constant variable

const usuario = { nombre: 'Ana', edad: 30 };
usuario.edad = 31;         // OK — modificamos el objeto
usuario = { nombre: 'Luis' };  // TypeError — cambiamos la referencia

const colores = ['rojo', 'verde'];
colores.push('azul');  // OK — ['rojo', 'verde', 'azul']
colores = [];          // TypeError

Si necesitas un objeto realmente inmutable, usa Object.freeze(). Ten en cuenta que el freeze es superficial: solo bloquea el primer nivel de propiedades.

Hoisting: var se eleva, let y const también, pero con trampa

var se inicializa como undefined al elevar la declaración. let y const también se elevan, pero entran en la Temporal Dead Zone y lanzan ReferenceError si se accede a ellas antes de la declaración:

console.log(a);  // undefined (var elevado)
var a = 5;

console.log(b);  // ReferenceError (TDZ)
let b = 5;

Cuándo usar cada una

La regla práctica que siguen la mayoría de equipos: usa const por defecto. Cambia a let solo cuando necesites reasignar la variable (contadores, acumuladores, variables de bucle). Evita var en código nuevo; solo aparece en código legado o cuando necesitas compatibilidad con entornos muy antiguos sin transpiler.

// Buenas prácticas modernas
const API_URL = 'https://api.ejemplo.com';
const usuarios = [];

function procesarLista(items) {
  let total = 0;
  for (const item of items) {
    total += item.precio;
  }
  return total;
}

Fíjate que en el bucle for...of usamos const para item: en cada iteración se crea un nuevo binding, así que la constante no se reasigna.

COMPARTE ESTE ARTÍCULO

COMPARTIR EN FACEBOOK
COMPARTIR EN TWITTER
COMPARTIR EN LINKEDIN
COMPARTIR EN WHATSAPP