Ir al contenido principal

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:
  1. Á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)
  1. 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;
  1. 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
  1. No tiene ámbito de bloque: Las variables declaradas con var dentro de bloques como if o for 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:
  1. Á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)
}
  1. 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;
  1. 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
  1. 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:
  1. Ámbito de bloque: Al igual que let, las variables declaradas con const 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)
  1. Debe inicializarse al declararla: A diferencia de var y let, 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
  1. 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
  1. 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 fijo
  • caracteresEscritos es una variable que necesita ser actualizada
  • textArea y contador son referencias a elementos DOM que no cambiarán
  • caracteresRestantes 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 y preciosProductos son constantes con valores fijos
  • carrito es un array que será modificado, pero la referencia se mantiene constante
  • subtotal 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 y opcionesFetch son constantes que no cambiarán
  • datos es una variable que inicialmente es null y luego se reasigna
  • respuesta es una constante que mantiene la respuesta del fetch
  • i 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:

  1. Tipos de declaración:

    • var: la forma tradicional, con ámbito de función y hoisting
    • let: introducido en ES6, con ámbito de bloque y sin redeclaración
    • const: también de ES6, para valores que no serán reasignados
  2. Á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
  3. Cuándo usar cada tipo:

    • var: generalmente