Declaración de variables en JavaScript
Introducción
Las variables son uno de los conceptos fundamentales en cualquier lenguaje de programación. Son como contenedores que nos permiten almacenar y manipular datos durante la ejecución de nuestro programa. En JavaScript, la forma en que declaramos estas variables ha evolucionado significativamente a lo largo de los años, pasando de tener una única palabra clave (var
) a contar con tres diferentes (var
, let
y const
), cada una con sus propias reglas y comportamientos. Esta evolución responde a la necesidad de solucionar problemas y limitaciones que presentaba la declaración tradicional con var
. En este artículo, exploraremos en profundidad cómo se declaran y utilizan las variables en JavaScript moderno, analizaremos las diferencias entre los tres tipos de declaraciones, y entenderemos cuándo es más apropiado utilizar cada una. Este conocimiento es fundamental para escribir código JavaScript robusto y libre de errores inesperados.
Diferencias entre var, let y const
JavaScript ofrece tres formas de declarar variables: var
, let
y const
. Aunque las tres permiten almacenar datos, tienen diferencias importantes que afectan cómo funcionan en nuestro código.
var
La palabra clave var
es la forma original de declarar variables en JavaScript, disponible desde las primeras versiones del lenguaje:
var edad = 25;
var nombre = "Ana";
var esEstudiante = true;
// Las variables declaradas con var pueden ser reasignadas
edad = 26;
nombre = "Carlos";
Características principales de var:
- Ámbito de función o global: Las variables declaradas con
var
tienen ámbito de función (si se declaran dentro de una función) o ámbito global (si se declaran fuera de cualquier función).
function ejemploVar() {
var x = 10;
console.log(x); // 10 (accesible dentro de la función)
}
ejemploVar();
console.log(x); // Error: x is not defined (no accesible fuera de la función)
- Hoisting: Las declaraciones con
var
son "elevadas" (hoisted) al principio de su ámbito, pero no sus inicializaciones.
console.log(a); // undefined (no causa error, la declaración fue elevada)
var a = 5;
// El código anterior es interpretado como:
var a;
console.log(a);
a = 5;
- Redeclaración permitida: Puedes declarar la misma variable múltiples veces sin error.
var contador = 1;
var contador = 2; // Válido, no causa error
console.log(contador); // 2
- No tiene ámbito de bloque: Las variables declaradas con
var
dentro de bloques comoif
ofor
son accesibles fuera de esos bloques.
if (true) {
var mensaje = "Hola desde dentro del if";
}
console.log(mensaje); // "Hola desde dentro del if" (accesible fuera del bloque)
let
La palabra clave let
fue introducida en ES6 (ECMAScript 2015) para solucionar varios problemas asociados con var
:
let edad = 25;
let nombre = "Ana";
let esEstudiante = true;
// Las variables declaradas con let pueden ser reasignadas
edad = 26;
nombre = "Carlos";
Características principales de let:
- Ámbito de bloque: Las variables declaradas con
let
tienen ámbito de bloque, lo que significa que solo son accesibles dentro del bloque donde se declaran.
function ejemploLet() {
let x = 10;
console.log(x); // 10 (accesible dentro de la función)
if (true) {
let y = 20;
console.log(y); // 20 (accesible dentro del if)
}
console.log(y); // Error: y is not defined (no accesible fuera del if)
}
- Hoisting parcial: Las declaraciones con
let
también son elevadas, pero no se inicializan. Intentar acceder a ellas antes de la declaración genera un error (Temporal Dead Zone).
console.log(a); // Error: Cannot access 'a' before initialization
let a = 5;
- No permite redeclaración en el mismo ámbito: No puedes declarar la misma variable dos veces en el mismo ámbito.
let contador = 1;
let contador = 2; // Error: Identifier 'contador' has already been declared
- Respeta el ámbito de bloque: Variables declaradas dentro de bloques como
if
,for
, o incluso bloques arbitrarios{}
, solo son accesibles dentro de ese bloque.
if (true) {
let mensaje = "Hola desde dentro del if";
}
console.log(mensaje); // Error: mensaje is not defined
const
La palabra clave const
también se introdujo en ES6 y se utiliza para declarar constantes, es decir, variables cuyo valor no puede ser reasignado:
const PI = 3.14159;
const DIAS_SEMANA = 7;
const NOMBRE_APLICACION = "Mi App";
// Intentar reasignar genera un error
PI = 3.14; // Error: Assignment to constant variable
Características principales de const:
- Ámbito de bloque: Al igual que
let
, las variables declaradas conconst
tienen ámbito de bloque.
if (true) {
const LIMITE = 100;
console.log(LIMITE); // 100 (accesible dentro del if)
}
console.log(LIMITE); // Error: LIMITE is not defined (no accesible fuera del if)
- Debe inicializarse al declararla: A diferencia de
var
ylet
, las constantes deben tener un valor asignado al momento de su declaración.
const TASA; // Error: Missing initializer in const declaration
TASA = 0.21; // Esto no funcionaría incluso si la línea anterior no diera error
- No permite reasignación: Una vez asignado un valor, no puede ser modificado.
const IVA = 0.21;
IVA = 0.19; // Error: Assignment to constant variable
- Los objetos y arrays son mutables: Aunque la variable en sí no puede ser reasignada, su contenido (si es un objeto o array) puede ser modificado.
const usuario = { nombre: "Ana", edad: 25 };
usuario.edad = 26; // Válido, no estamos reasignando la variable usuario
console.log(usuario); // { nombre: "Ana", edad: 26 }
usuario = { nombre: "Carlos", edad: 30 }; // Error: Assignment to constant variable
const numeros = [1, 2, 3];
numeros.push(4); // Válido, no estamos reasignando la variable numeros
console.log(numeros); // [1, 2, 3, 4]
Ámbito de cada tipo de declaración
El concepto de "ámbito" (scope) es fundamental para entender cómo funcionan las variables en JavaScript. El ámbito determina dónde es accesible una variable dentro de nuestro código.
Ámbito global
Las variables declaradas fuera de cualquier función o bloque tienen ámbito global, lo que significa que son accesibles desde cualquier parte del código:
// Variables globales
var variableGlobalVar = "Accesible en todas partes con var";
let variableGlobalLet = "Accesible en todas partes con let";
const CONSTANTE_GLOBAL = "Accesible en todas partes con const";
function ejemploAccesoGlobal() {
console.log(variableGlobalVar); // Accesible
console.log(variableGlobalLet); // Accesible
console.log(CONSTANTE_GLOBAL); // Accesible
}
Ámbito de función
Las variables declaradas dentro de una función solo son accesibles dentro de esa función:
function ejemploFuncion() {
var x = "Declarada con var dentro de función";
let y = "Declarada con let dentro de función";
const Z = "Declarada con const dentro de función";
console.log(x); // Accesible
console.log(y); // Accesible
console.log(Z); // Accesible
}
ejemploFuncion();
console.log(x); // Error: x is not defined
console.log(y); // Error: y is not defined
console.log(Z); // Error: Z is not defined
Ámbito de bloque
Un bloque en JavaScript está delimitado por llaves {}
. Los bloques pueden ser parte de estructuras de control como if
, for
, while
, o incluso bloques independientes.
La gran diferencia entre var
por un lado, y let
/const
por otro, es cómo manejan el ámbito de bloque:
if (true) {
var mensajeVar = "Declarado con var en un if";
let mensajeLet = "Declarado con let en un if";
const MENSAJE_CONST = "Declarado con const en un if";
}
console.log(mensajeVar); // "Declarado con var en un if" (accesible fuera del bloque)
console.log(mensajeLet); // Error: mensajeLet is not defined (no accesible fuera del bloque)
console.log(MENSAJE_CONST); // Error: MENSAJE_CONST is not defined (no accesible fuera del bloque)
Esta diferencia es especialmente relevante en bucles:
// Con var
for (var i = 0; i < 3; i++) {
// Código del bucle
}
console.log(i); // 3 (accesible fuera del bucle)
// Con let
for (let j = 0; j < 3; j++) {
// Código del bucle
}
console.log(j); // Error: j is not defined (no accesible fuera del bucle)
Anidamiento de ámbitos
Los ámbitos pueden anidarse, y las variables son accesibles desde ámbitos internos a externos:
function externa() {
const mensaje = "Declarada en función externa";
function interna() {
const respuesta = "Declarada en función interna";
console.log(mensaje); // Accesible (ámbito externo)
console.log(respuesta); // Accesible (ámbito actual)
}
interna();
console.log(mensaje); // Accesible (ámbito actual)
console.log(respuesta); // Error: respuesta is not defined (ámbito interno)
}
Cuándo usar cada tipo de declaración
Elegir entre var
, let
y const
puede parecer confuso al principio, pero hay pautas claras que puedes seguir:
Uso de var
Debido a los problemas potenciales que puede causar, generalmente se recomienda evitar var
en código moderno. Las razones principales son:
- No respeta el ámbito de bloque, lo que puede causar bugs difíciles de detectar
- El comportamiento de hoisting puede llevar a comportamientos inesperados
- Permite redeclaración de variables, lo que puede causar sobrescrituras accidentales
Sin embargo, hay situaciones específicas donde var
puede ser útil:
- Cuando necesitas compatibilidad con navegadores muy antiguos que no soportan ES6
- En ciertos patrones de código antiguos que dependen de su comportamiento específico
Uso de let
let
es la opción recomendada para variables que necesitan ser reasignadas:
// Variables que cambiarán de valor
let contador = 0;
let nombreUsuario = "";
let estaLogueado = false;
// En bucles
for (let i = 0; i < array.length; i++) {
// Código del bucle
}
// Para acumuladores
let suma = 0;
for (let numero of numeros) {
suma += numero;
}
let
es especialmente útil en:
- Contadores e iteradores
- Variables que almacenan estado temporal
- Valores que serán sobrescritos
- Variables que reciben input del usuario
Uso de const
const
es la opción recomendada por defecto para la mayoría de las variables, especialmente cuando:
// Valores que no cambiarán
const PI = 3.14159;
const URL_API = "https://api.ejemplo.com";
const DIAS_POR_SEMANA = 7;
// Objetos y arrays (aunque su contenido pueda cambiar)
const usuario = { nombre: "Ana", edad: 25 };
const colores = ["rojo", "verde", "azul"];
// Funciones (especialmente arrow functions)
const sumar = (a, b) => a + b;
const
es ideal para:
- Valores realmente constantes (como PI, tasas de impuestos, etc.)
- Referencias a funciones
- Objetos y arrays (aunque sus propiedades/elementos se modifiquen)
- Configuraciones e inicializaciones
- La mayoría de las importaciones de módulos
Regla práctica
Una buena regla práctica es usar const
por defecto y cambiar a let
solo cuando necesites reasignar la variable:
// Intenta usar const primero
const respuesta = obtenerDatos();
const elementosDOM = document.querySelectorAll('.item');
const configuracion = { tema: 'claro', idioma: 'es' };
// Usa let cuando necesites reasignar
let intentos = 3;
while (intentos > 0 && !exitoOperacion) {
intentos--;
// Intentar operación
}
Esta práctica reduce la posibilidad de errores causados por reasignaciones accidentales y hace que el código sea más fácil de entender, ya que queda claro qué variables se espera que cambien y cuáles no.
Redeclaración y reasignación
Es importante entender qué operaciones están permitidas con cada tipo de declaración:
Reasignación
La reasignación significa asignar un nuevo valor a una variable existente:
var a = 1;
a = 2; // Reasignación permitida con var
let b = 1;
b = 2; // Reasignación permitida con let
const c = 1;
c = 2; // Error: Assignment to constant variable (reasignación no permitida con const)
Redeclaración
La redeclaración significa declarar nuevamente una variable con el mismo nombre en el mismo ámbito:
var d = 1;
var d = 2; // Redeclaración permitida con var
let e = 1;
let e = 2; // Error: Identifier 'e' has already been declared (redeclaración no permitida con let)
const f = 1;
const f = 2; // Error: Identifier 'f' has already been declared (redeclaración no permitida con const)
Redeclaración en diferentes ámbitos
Es importante notar que tanto let
como const
permiten redeclarar variables con el mismo nombre en diferentes ámbitos:
let g = "global";
if (true) {
let g = "local"; // Válido, es un ámbito diferente
console.log(g); // "local"
}
console.log(g); // "global"
Mutación vs. reasignación
Con const
, es fundamental entender la diferencia entre reasignación (no permitida) y mutación (permitida):
// Reasignación (no permitida con const)
const usuario = { nombre: "Ana" };
usuario = { nombre: "Carlos" }; // Error: Assignment to constant variable
// Mutación (permitida con const)
const usuario = { nombre: "Ana" };
usuario.nombre = "Carlos"; // Válido, estamos mutando el objeto, no reasignando la variable
usuario.edad = 25; // Válido, añadiendo una nueva propiedad
// Lo mismo aplica a arrays
const numeros = [1, 2, 3];
numeros.push(4); // Válido, mutación
numeros = [5, 6, 7]; // Error: Assignment to constant variable
Para prevenir incluso la mutación, puedes usar métodos como Object.freeze()
:
const usuarioInmutable = Object.freeze({ nombre: "Ana", edad: 25 });
usuarioInmutable.edad = 26; // No funcionará (en modo estricto lanza un error)
console.log(usuarioInmutable.edad); // 25 (el valor no cambió)
Sin embargo, Object.freeze()
solo es superficial; no congela objetos anidados.
Ejemplos prácticos
Veamos algunos ejemplos prácticos que ilustran cuándo es mejor usar cada tipo de declaración:
Ejemplo 1: Contador de caracteres
function contadorCaracteres() {
const maximoCaracteres = 280; // Valor constante que no cambiará
let caracteresEscritos = 0; // Variable que será reasignada
const textArea = document.getElementById('mensaje');
const contador = document.getElementById('contador');
textArea.addEventListener('input', function() {
caracteresEscritos = textArea.value.length;
const caracteresRestantes = maximoCaracteres - caracteresEscritos;
contador.textContent = `${caracteresRestantes} caracteres restantes`;
if (caracteresRestantes < 0) {
contador.classList.add('error');
} else {
contador.classList.remove('error');
}
});
}
En este ejemplo:
maximoCaracteres
es una constante que define un límite fijocaracteresEscritos
es una variable que necesita ser actualizadatextArea
ycontador
son referencias a elementos DOM que no cambiaráncaracteresRestantes
se recalcula en cada evento, pero no necesita ser accesible fuera del callback
Ejemplo 2: Carrito de compra
function gestionarCarrito() {
const IVA = 0.21; // Constante que no cambiará
const preciosProductos = { // Objeto con datos fijos
"laptop": 1200,
"smartphone": 800,
"auriculares": 100
};
const carrito = []; // Array que será mutado, pero la referencia no cambiará
// Función para añadir producto al carrito
function agregarProducto(id, cantidad) {
carrito.push({ id, cantidad });
actualizarInterfaz();
}
// Función para calcular el total
function calcularTotal() {
let subtotal = 0; // Variable que acumulará valores
for (const item of carrito) {
const precio = preciosProductos[item.id];
subtotal += precio * item.cantidad;
}
const impuestos = subtotal * IVA;
const total = subtotal + impuestos;
return { subtotal, impuestos, total };
}
function actualizarInterfaz() {
const { subtotal, impuestos, total } = calcularTotal();
document.getElementById('subtotal').textContent = `${subtotal.toFixed(2)} €`;
document.getElementById('impuestos').textContent = `${impuestos.toFixed(2)} €`;
document.getElementById('total').textContent = `${total.toFixed(2)} €`;
}
// Configurar event listeners, etc.
}
En este ejemplo:
IVA
ypreciosProductos
son constantes con valores fijoscarrito
es un array que será modificado, pero la referencia se mantiene constantesubtotal
es una variable que acumula valores, necesita ser reasignada- Los objetos devueltos y desestructurados usan constantes para sus valores
Ejemplo 3: Fetch de datos
async function obtenerDatos() {
const URL_API = 'https://api.ejemplo.com/datos'; // URL fija
const opcionesFetch = { // Configuración constante
method: 'GET',
headers: {
'Content-Type': 'application/json',
'Authorization': 'Bearer token123'
}
};
try {
let datos = null; // Variable que será reasignada con los datos
const respuesta = await fetch(URL_API, opcionesFetch);
if (!respuesta.ok) {
throw new Error(`Error HTTP: ${respuesta.status}`);
}
datos = await respuesta.json();
// Procesar los datos
for (let i = 0; i < datos.length; i++) {
datos[i].fecha = new Date(datos[i].timestamp);
}
return datos;
} catch (error) {
console.error('Error al obtener datos:', error);
return [];
}
}
En este ejemplo:
URL_API
yopcionesFetch
son constantes que no cambiarándatos
es una variable que inicialmente es null y luego se reasignarespuesta
es una constante que mantiene la respuesta del fetchi
es una variable de iteración en el bucle for
Errores comunes y cómo evitarlos
Al trabajar con variables en JavaScript, hay varios errores comunes que pueden surgir. Veamos cómo identificarlos y evitarlos:
1. Acceder a variables antes de su declaración
Con let
y const
, intentar acceder a variables antes de su declaración (en la "Temporal Dead Zone") causa un error:
console.log(valor); // Error: Cannot access 'valor' before initialization
let valor = 5;
Solución: Siempre declara las variables antes de usarlas:
let valor = 5;
console.log(valor); // 5
2. Redeclarar variables con let o const
Intentar redeclarar variables con let
o const
en el mismo ámbito causa errores:
let contador = 1;
let contador = 2; // Error: Identifier 'contador' has already been declared
Solución: Usa nombres únicos para tus variables o reasigna la variable existente:
let contador = 1;
contador = 2; // Reasignación, no redeclaración
3. No inicializar constantes
Las constantes deben inicializarse al momento de su declaración:
const IVA; // Error: Missing initializer in const declaration
IVA = 0.21;
Solución: Asigna un valor al declarar la constante:
const IVA = 0.21;
4. Confundir ámbito de bloque
Un error común es asumir que las variables declaradas con var
tienen ámbito de bloque:
if (true) {
var secreto = "Información sensible";
}
console.log(secreto); // "Información sensible" (accesible fuera del bloque)
Solución: Usa let
o const
para variables que deberían tener ámbito de bloque:
if (true) {
const secreto = "Información sensible";
}
console.log(secreto); // Error: secreto is not defined
5. Intentar reasignar constantes
Intentar cambiar el valor de una constante causa un error:
const API_KEY = "abc123";
API_KEY = "xyz789"; // Error: Assignment to constant variable
Solución: Si necesitas cambiar el valor, usa let
en lugar de const
:
let apiKey = "abc123";
apiKey = "xyz789"; // Válido
6. Confundir reasignación con mutación
Un error sutil es confundir la reasignación (no permitida en constantes) con la mutación de objetos o arrays (que sí está permitida):
const usuario = { nombre: "Ana" };
usuario = { nombre: "Juan" }; // Error: reasignación no permitida
const usuario = { nombre: "Ana" };
usuario.nombre = "Juan"; // Válido: mutación permitida
Solución: Entiende la diferencia y usa Object.freeze()
si necesitas prevenir mutaciones:
const usuarioInmutable = Object.freeze({ nombre: "Ana" });
usuarioInmutable.nombre = "Juan"; // No tendrá efecto (o causará error en modo estricto)
7. Variables no declaradas
Asignar un valor a una variable sin declararla crea una variable global implícita (en modo no estricto):
function funcion() {
x = 10; // Crea una variable global implícita si no estamos en modo estricto
}
funcion();
console.log(x); // 10
Solución: Siempre declara tus variables explícitamente y usa el modo estricto:
"use strict";
function funcion() {
let x = 10; // Correctamente declarada
}
Buenas prácticas con variables
Para finalizar, repasemos algunas buenas prácticas que te ayudarán a escribir código más limpio y menos propenso a errores:
1. Preferir const por defecto
Usa const
como opción predeterminada para tus variables y cambia a let
solo cuando necesites reasignar valores. Esto hace tu código más previsible y menos propenso a errores.
// ✅ Buena práctica
const usuarios = obtenerUsuarios();
const elemento = document.querySelector('#miElemento');
const config = { tema: 'oscuro' };
// Usa let solo cuando sea necesario
let contador = 0;
contador++;
2. Evitar var en código moderno
Salvo que necesites compatibilidad con navegadores muy antiguos, evita usar var
en código nuevo. Usar let
y const
te ayudará a evitar muchos problemas sutiles.
// ❌ Evitar
var datos = [];
// ✅ Preferir
const datos = [];
3. Declarar variables al principio de su ámbito
Declara tus variables al principio de la función o bloque donde las usarás. Esto mejora la legibilidad y evita problemas con la "Temporal Dead Zone".
// ✅ Buena práctica
function procesarDatos(entrada) {
const MAX_ITEMS = 100;
let resultado = [];
let contador = 0;
// Resto del código...
}
4. Usar nombres descriptivos
Elige nombres que describan claramente el propósito de la variable. Evita nombres de una sola letra excepto en casos muy específicos (como índices en bucles cortos).
// ❌ Evitar
const x = obtenerDatos();
const arr = [1, 2, 3];
// ✅ Preferir
const datosUsuario = obtenerDatos();
const numeros = [1, 2, 3];
5. Agrupar declaraciones relacionadas
Agrupa las declaraciones de variables relacionadas para mejorar la legibilidad del código.
// ✅ Buena práctica: agrupar por categorías
// Configuración
const MAX_INTENTOS = 3;
const TIEMPO_ESPERA = 1000;
// Estado
let intentosActuales = 0;
let esperando = false;
6. Minimizar el ámbito de las variables
Declara las variables en el ámbito más reducido posible donde sean necesarias. Esto reduce la posibilidad de conflictos y hace el código más modular.
// ❌ Evitar ámbitos innecesariamente amplios
const mensaje = "Hola";
function procesarDatos() {
// La variable mensaje no se usa aquí
}
// ✅ Preferir ámbitos más reducidos
function mostrarMensaje() {
const mensaje = "Hola";
console.log(mensaje);
}
7. Usar desestructuración para extraer propiedades
La desestructuración de objetos y arrays hace tu código más limpio cuando necesitas extraer múltiples propiedades.
// ❌ Sin desestructuración
const nombre = usuario.nombre;
const edad = usuario.edad;
const ciudad = usuario.direccion.ciudad;
// ✅ Con desestructuración
const { nombre, edad, direccion: { ciudad } } = usuario;
8. Evitar variables globales
Minimiza el uso de variables globales, ya que pueden ser modificadas desde cualquier parte del código, lo que dificulta el seguimiento de los cambios.
// ❌ Evitar
window.datosUsuario = { nombre: "Ana" };
// ✅ Preferir módulos y ámbitos locales
// modulo-usuario.js
const datosUsuario = { nombre: "Ana" };
export function obtenerUsuario() {
return datosUsuario;
}
9. Usar siempre el modo estricto
El modo estricto ("use strict"
) ayuda a prevenir comportamientos problemáticos y errores comunes con variables.
"use strict";
function ejemplo() {
x = 10; // Error en modo estricto: x is not defined
}
10. Declarar una sola variable por línea
Declara cada variable en su propia línea para mejorar la legibilidad y facilitar la adición o eliminación de variables.
// ❌ Evitar
let a = 1, b = 2, c = 3;
// ✅ Preferir
let a = 1;
let b = 2;
let c = 3;
Resumen
En este artículo, hemos explorado en profundidad la declaración de variables en JavaScript:
-
Tipos de declaración:
var
: la forma tradicional, con ámbito de función y hoistinglet
: introducido en ES6, con ámbito de bloque y sin redeclaraciónconst
: también de ES6, para valores que no serán reasignados
-
Ámbitos:
- Global: Variables accesibles en todo el programa
- Función: Variables solo accesibles dentro de la función
- Bloque: Variables (
let
/const
) solo accesibles dentro del bloque
-
Cuándo usar cada tipo:
var
: generalmente