Módulo 02: Variables y tipos de datos
Objetivos del módulo
- Entender qué es una variable y cómo se declara, se inicializa y se reasigna
- Usar
letyconstcorrectamente, y saber por quéconstno significa inmutable - Conocer
vary sus problemas para poder leer código antiguo - Aplicar las reglas y convenciones de nombres de JavaScript
- Conocer los siete tipos primitivos y el tipo
object, y usartypeof(con sus rarezas) - Dominar el tipo
number: decimales imprecisos,NaN,Infinity, enteros seguros, separadores numéricos ytoFixed - Usar
Math.sumPrecise(ES2026) yBigIntcuando los números normales no bastan - Distinguir valores truthy y falsy, y
nullfrente aundefined - Convertir tipos de forma explícita y reconocer las trampas de la coerción implícita
- Comprender la diferencia entre valor y referencia, y la zona muerta temporal (TDZ)
1. ¿Qué es una variable?
📘 Concepto: Una variable es un nombre que apunta a un valor guardado en la memoria del ordenador. Piensa en una caja con una etiqueta: la etiqueta es el nombre (
edad) y dentro de la caja está el dato (28). Cuando el programa necesita el dato, lo busca por su etiqueta.
let edad = 28; // declaramos la variable "edad" y le damos el valor 28
console.log(edad); // 28
edad = 29; // cambiamos su valor (reasignación)
console.log(edad); // 29
console.log(edad + 1); // 30 (usamos su valor sin cambiarlo)
Tres conceptos que conviene distinguir desde el principio:
| Término | Qué es | Ejemplo |
|---|---|---|
| Declarar | Crear la variable (reservar el nombre) | let precio; |
| Inicializar | Darle su primer valor | let precio = 10; |
| Asignar / reasignar | Darle un valor nuevo con = | precio = 12; |
let mensaje; // declarada pero sin valor
console.log(mensaje); // undefined (el valor "sin valor" de JavaScript)
mensaje = 'Hola'; // ahora tiene valor
console.log(mensaje); // Hola
⚠️ Importante: El signo
=en programación no significa “es igual a” como en matemáticas, sino “guarda en la variable de la izquierda el valor de la derecha”. Por esocontador = contador + 1tiene sentido: calculacontador + 1y guarda el resultado encontador.
2. let y const
Desde ES2015, JavaScript tiene dos formas modernas de declarar variables.
2.1 let: variables que pueden cambiar
let puntos = 0;
puntos = puntos + 10;
puntos = puntos + 5;
console.log(puntos); // 15
2.2 const: variables que no se pueden reasignar
const IVA = 0.21;
const nombre = 'Ana';
nombre = 'Luis'; // ❌ TypeError: Assignment to constant variable.
Una const debe inicializarse al declararla:
const pi; // ❌ SyntaxError: Missing initializer in const declaration
2.3 const no significa inmutable
Esta es una de las confusiones más habituales. const impide reasignar la variable (cambiar lo que hay en la caja por otra cosa), pero no impide modificar el contenido de un objeto o un array que esté dentro:
const usuario = { nombre: 'Ana', edad: 28 };
usuario.edad = 29; // ✅ permitido: modificamos el objeto
console.log(usuario); // { nombre: 'Ana', edad: 29 }
usuario = { nombre: 'Luis' }; // ❌ TypeError: Assignment to constant variable.
const colores = ['rojo', 'verde'];
colores.push('azul'); // ✅ permitido: modificamos el array
console.log(colores); // [ 'rojo', 'verde', 'azul' ]
📘 Concepto: Piensa en
constcomo una caja pegada con cola a su etiqueta: la etiquetausuariosiempre apuntará al mismo objeto, pero ese objeto puede cambiar por dentro. Si quieres un objeto que no se pueda modificar, existeObject.freeze()(lo verás en el módulo 07).
2.4 ¿Cuál uso?
💡 Consejo: La regla profesional es sencilla: usa
constpor defecto y cambia aletsolo cuando necesites reasignar la variable (contadores, acumuladores, valores que cambian en un bucle). Así, quien lea tu código sabe que lo que esconstno va a cambiar de valor más adelante, y te ahorras muchos errores.
const precioBase = 100; // no cambia
const descuento = 0.15; // no cambia
let total = precioBase; // va a cambiar
total = total - total * descuento;
console.log(total); // 85
2.5 Ámbito de bloque
let y const tienen ámbito de bloque: solo existen dentro de las llaves { } donde se declaran.
const edad = 20;
if (edad >= 18) {
const mensaje = 'Eres mayor de edad';
console.log(mensaje); // Eres mayor de edad
}
console.log(mensaje); // ❌ ReferenceError: mensaje is not defined
Profundizarás en el ámbito en el módulo 05.
3. var y sus problemas
Antes de 2015 la única forma de declarar variables era var. Hoy no debes usarla, pero la encontrarás en tutoriales antiguos y en código heredado, así que tienes que conocer sus trampas.
3.1 Problema 1: ámbito de función, no de bloque
if (true) {
var secreto = 'visible fuera';
}
console.log(secreto); // visible fuera ← ¡se "escapa" del bloque!
Las variables var solo respetan los límites de una función, no los de un if o un for. Un ejemplo clásico de error:
for (var i = 0; i < 3; i++) {
setTimeout(() => console.log('var:', i), 0);
}
// var: 3
// var: 3
// var: 3 ← las tres comparten la misma i, que al final vale 3
for (let j = 0; j < 3; j++) {
setTimeout(() => console.log('let:', j), 0);
}
// let: 0
// let: 1
// let: 2 ← cada vuelta tiene su propia j
3.2 Problema 2: hoisting con valor undefined
📘 Concepto: Hoisting (elevación) es el comportamiento por el que JavaScript procesa las declaraciones antes de ejecutar el código, como si las “subiera” al principio de su ámbito.
Con var, la declaración sube pero el valor no, así que puedes usar la variable antes de declararla y obtienes undefined en vez de un error:
console.log(ciudad); // undefined (¡sin error!)
var ciudad = 'Madrid';
console.log(ciudad); // Madrid
Es como si JavaScript hubiera reescrito tu código así:
var ciudad; // la declaración "sube"
console.log(ciudad); // undefined
ciudad = 'Madrid'; // la asignación se queda en su sitio
Esto oculta errores: usar una variable antes de darle valor casi siempre es un fallo, y var no te avisa.
3.3 Problema 3: redeclaración
var total = 100;
// ... 200 líneas después, otra persona escribe:
var total = 0; // ✅ sin error: ¡acaba de machacar la variable anterior!
let precio = 100;
let precio = 0; // ❌ SyntaxError: Identifier 'precio' has already been declared
3.4 Problema 4: variables globales en el objeto window
En un script clásico del navegador (no módulo), un var en el nivel superior se convierte en propiedad de window, y puede chocar con propiedades del propio navegador:
var name = 'Ana';
console.log(window.name); // Ana ← hemos sobrescrito una propiedad del navegador
3.5 Tabla comparativa
| Característica | var | let | const |
|---|---|---|---|
| Ámbito | Función | Bloque | Bloque |
| Hoisting | Sí, inicializada a undefined | Sí, pero en TDZ (error si se usa antes) | Sí, pero en TDZ |
| Redeclarar en el mismo ámbito | Permitido | Error | Error |
| Reasignar | Sí | Sí | No |
| Obligatorio inicializar | No | No | Sí |
Crea propiedad en window (script clásico) | Sí | No | No |
| Uso recomendado | Ninguno (solo código antiguo) | Valores que cambian | Por defecto |
⚠️ Importante: En este curso nunca usaremos
varsalvo para explicarlo. ESLint tiene una regla (no-var) que te avisa si lo usas.
4. Nombres de variables
4.1 Reglas obligatorias
Si no las cumples, el código da error de sintaxis:
- Pueden contener letras, dígitos,
_(guion bajo) y$(dólar). - No pueden empezar por un dígito:
1usuario❌,usuario1✅. - No pueden contener espacios ni guiones:
mi nombre❌,mi-nombre❌ (el guion es la resta). - No pueden ser palabras reservadas:
let,const,if,for,function,class,return,true,null,new,this,typeof… - Distinguen mayúsculas y minúsculas:
edad,EdadyEDADson tres variables distintas.
const nombre1 = 'válido';
const _privado = 'válido';
const $elemento = 'válido';
const precioConIVA = 'válido';
// const 2precio = 'error'; ❌ empieza por dígito
// const precio-final = 'error'; ❌ contiene guion
// const class = 'error'; ❌ palabra reservada
📘 Concepto: JavaScript admite letras Unicode en los nombres, así que
const año = 2026;es válido. Aun así, en la práctica profesional se evitan las tildes y la eñe en los identificadores (por ejemplo,aniooyear) para no tener problemas con teclados, herramientas o equipos internacionales.
4.2 Convenciones (no obligatorias, pero universales)
| Convención | Cuándo se usa | Ejemplo |
|---|---|---|
| camelCase | Variables y funciones (lo normal) | precioTotal, nombreUsuario, calcularMedia |
| PascalCase | Clases y constructores | Usuario, CarritoCompra |
| UPPER_SNAKE_CASE | Constantes “de configuración” conocidas antes de ejecutar | MAX_INTENTOS, URL_API, IVA_GENERAL |
Prefijo es, tiene, puede | Booleanos | esAdmin, tieneStock, puedeEditar |
| Plural | Colecciones | usuarios, productos |
const MAX_INTENTOS = 3; // configuración fija
const nombreUsuario = 'ana_garcia'; // camelCase
let intentosRestantes = MAX_INTENTOS;
const esAdmin = false; // booleano con prefijo "es"
const productos = ['pan', 'leche']; // plural para colecciones
4.3 Nombres que explican
// ❌ Nombres crípticos
const d = 7;
const x = d * 24;
// ✅ Nombres que cuentan qué guardan
const diasDeLaSemana = 7;
const horasPorSemana = diasDeLaSemana * 24;
💡 Consejo: Un buen nombre ahorra un comentario. Si dudas entre un nombre corto y uno largo pero claro, elige el claro. La única excepción aceptada son contadores de bucles muy cortos como
ioj.
5. Tipos de datos
📘 Concepto: Cada valor en JavaScript tiene un tipo, que indica qué clase de dato es y qué operaciones puedes hacer con él. No es lo mismo sumar dos números que “sumar” dos textos.
JavaScript tiene siete tipos primitivos y un tipo no primitivo (object):
| Tipo | Qué representa | Ejemplos |
|---|---|---|
number | Números enteros y decimales | 42, 3.14, -7, NaN, Infinity |
string | Texto | 'Hola', "Adiós", `Hola ${nombre}` |
boolean | Verdadero o falso | true, false |
undefined | Variable sin valor asignado | undefined |
null | Ausencia intencionada de valor | null |
bigint | Enteros de tamaño arbitrario | 9007199254740993n |
symbol | Identificador único | Symbol('id') |
object | Colecciones de datos: objetos, arrays, funciones, fechas… | { nombre: 'Ana' }, [1, 2, 3] |
📘 Concepto: Un primitivo es un valor simple e indivisible (un número, un texto, un booleano). Un objeto es una estructura que agrupa varios valores. La diferencia es importante para entender cómo se copian (sección 14).
5.1 El operador typeof
typeof devuelve, como texto, el tipo de un valor:
console.log(typeof 42); // number
console.log(typeof 'hola'); // string
console.log(typeof true); // boolean
console.log(typeof undefined); // undefined
console.log(typeof 10n); // bigint
console.log(typeof Symbol('id')); // symbol
console.log(typeof { a: 1 }); // object
5.2 Las rarezas de typeof
console.log(typeof null); // object ← ¡un error histórico!
console.log(typeof []); // object (los arrays son objetos)
console.log(typeof function () {}); // function (las funciones son objetos, pero typeof las distingue)
console.log(typeof NaN); // number ("no es un número"... es de tipo number)
console.log(typeof noDeclarada); // undefined (no da error con variables no declaradas)
console.log(typeof typeof 1); // string (typeof siempre devuelve un texto)
⚠️ Importante:
typeof null === 'object'es un error de la primera versión de JavaScript (1995) que nunca se corrigió porque arreglarlo rompería millones de webs. Para comprobar si algo esnull, compáralo directamente:valor === null. Para saber si algo es un array, usaArray.isArray(valor).
| Comprobación | Forma correcta |
|---|---|
| ¿Es un número? | typeof x === 'number' (y !Number.isNaN(x) si quieres excluir NaN) |
| ¿Es un texto? | typeof x === 'string' |
¿Es null? | x === null |
¿Es undefined? | x === undefined |
| ¿Es un array? | Array.isArray(x) |
| ¿Es una función? | typeof x === 'function' |
| ¿Es un objeto “de verdad”? | typeof x === 'object' && x !== null |
6. El tipo number en detalle
6.1 Un solo tipo para enteros y decimales
A diferencia de otros lenguajes, JavaScript no distingue entre enteros y decimales: todos son number. Internamente se guardan en formato IEEE 754 de doble precisión (64 bits), el mismo estándar que usan casi todos los procesadores.
const entero = 42;
const decimal = 3.14; // el separador decimal es el PUNTO, nunca la coma
const negativo = -7;
const cientifico = 2.5e-3; // 0.0025 (notación científica)
const hexadecimal = 0xff; // 255
const binario = 0b1010; // 10
const octal = 0o17; // 15
6.2 Separadores numéricos (ES2021)
Para leer mejor números largos puedes usar _ como separador visual. No cambia el valor:
const poblacion = 48_000_000;
const precioMillones = 1_250_000.50;
const microsegundo = 0.000_001;
console.log(poblacion); // 48000000
6.3 La famosa suma 0.1 + 0.2
console.log(0.1 + 0.2); // 0.30000000000000004
console.log(0.1 + 0.2 === 0.3); // false
📘 Concepto: No es un error de JavaScript: le pasa a todos los lenguajes que usan IEEE 754 (Python, Java, C#…). El ordenador guarda los números en binario, y igual que 1/3 no se puede escribir exactamente en decimal (0,3333…), 0,1 no se puede escribir exactamente en binario. Se guarda el número binario más cercano, que es
0.1000000000000000055511..., y al sumar, esos pequeños errores se notan.
console.log((0.1).toFixed(20)); // 0.10000000000000000555 (el valor real guardado)
Cómo comparar decimales de forma segura:
const resultado = 0.1 + 0.2;
// Comparar con una tolerancia muy pequeña
console.log(Math.abs(resultado - 0.3) < Number.EPSILON); // true
6.4 Redondear para mostrar: toFixed
toFixed(n) devuelve un texto con n decimales, redondeando:
const total = 0.1 + 0.2;
console.log(total.toFixed(2)); // 0.30
console.log(typeof total.toFixed(2)); // string ← ¡es un texto, no un número!
console.log((1234.5678).toFixed(1)); // 1234.6
console.log((1.005).toFixed(2)); // 1.00 ← 1.005 se guarda como 1.00499999...
⚠️ Importante:
toFixedsirve para mostrar números, no para calcular con ellos. Y como ves con1.005, incluso el redondeo sufre la imprecisión binaria. Para mostrar cantidades con formato local (por ejemplo1.234,57 €) usarásIntl.NumberFormaten el módulo 19.
6.5 Los valores especiales: NaN e Infinity
console.log(1 / 0); // Infinity
console.log(-1 / 0); // -Infinity
console.log(0 / 0); // NaN
console.log('abc' * 2); // NaN
console.log(Math.sqrt(-1)); // NaN
NaN (Not a Number) es el resultado de una operación matemática que no tiene sentido. Tiene una propiedad única: no es igual a nada, ni siquiera a sí mismo.
console.log(NaN === NaN); // false ← ¡nunca compares con NaN así!
console.log(Number.isNaN(NaN)); // true ✅ la forma correcta
// La función global antigua isNaN() convierte antes a número: engaña
console.log(isNaN('hola')); // true (convierte 'hola' en NaN)
console.log(Number.isNaN('hola')); // false ('hola' es un string, no NaN)
💡 Consejo: Usa siempre
Number.isNaN()yNumber.isFinite()en lugar de las funciones globalesisNaN()eisFinite(), que hacen conversiones automáticas y dan resultados confusos.
6.6 Enteros: Number.isInteger y los límites seguros
console.log(Number.isInteger(5)); // true
console.log(Number.isInteger(5.0)); // true (5.0 es lo mismo que 5)
console.log(Number.isInteger(5.5)); // false
console.log(Number.isInteger('5')); // false (es un texto)
Como los números usan 64 bits, solo se pueden representar con exactitud los enteros entre -(2^53 - 1) y 2^53 - 1:
console.log(Number.MAX_SAFE_INTEGER); // 9007199254740991
console.log(Number.MIN_SAFE_INTEGER); // -9007199254740991
console.log(Number.MAX_SAFE_INTEGER + 1); // 9007199254740992
console.log(Number.MAX_SAFE_INTEGER + 2); // 9007199254740992 ← ¡el mismo! Se pierde precisión
console.log(9007199254740993); // 9007199254740992
console.log(Number.isSafeInteger(2 ** 53)); // false
| Constante | Valor | Significado |
|---|---|---|
Number.MAX_SAFE_INTEGER | 9007199254740991 | Mayor entero exacto |
Number.MIN_SAFE_INTEGER | -9007199254740991 | Menor entero exacto |
Number.MAX_VALUE | 1.7976931348623157e+308 | Mayor número representable |
Number.MIN_VALUE | 5e-324 | Menor positivo representable |
Number.EPSILON | 2.220446049250313e-16 | Diferencia mínima entre 1 y el siguiente número |
⚠️ Importante: Los identificadores enormes que vienen de bases de datos o APIs (por ejemplo, IDs de 18 dígitos) no caben en un
numbersin perder precisión. Guárdalos como texto o como BigInt (sección 8).
7. Sumas imprecisas y Math.sumPrecise (ES2026)
7.1 El problema de acumular errores
Cuando sumas muchos decimales uno a uno, cada suma intermedia se redondea y los errores se acumulan:
const importes = [0.1, 0.2, 0.3];
let total = 0;
for (const importe of importes) {
total += importe;
}
console.log(total); // 0.6000000000000001
En casos extremos, el error se come un número entero:
const valores = [1e20, 0.1, -1e20];
let suma = 0;
for (const v of valores) {
suma += v;
}
console.log(suma); // 0 ← ¡el 0.1 ha desaparecido!
7.2 La solución de ES2026: Math.sumPrecise
Math.sumPrecise(iterable) recibe un array (o cualquier iterable) de números y calcula la suma como si tuviera precisión infinita, redondeando una sola vez al final:
console.log(Math.sumPrecise([0.1, 0.2, 0.3])); // 0.6
console.log(Math.sumPrecise([1e20, 0.1, -1e20])); // 0.1
console.log(Math.sumPrecise([])); // -0 (suma vacía)
⚠️ Importante:
Math.sumPreciseevita los errores de la suma, pero no puede arreglar que0.1y0.2ya estuvieran guardados de forma aproximada. Por esoMath.sumPrecise([0.1, 0.2])sigue dando0.30000000000000004: es el número más cercano a la suma exacta de esos dos valores binarios. Donde más ayuda es al sumar muchos valores o valores de magnitudes muy distintas.
Otras características:
- Solo acepta números: si algún elemento no es
numberlanza unTypeError(no convierte textos como'5'). - Recibe un iterable, no varios argumentos:
Math.sumPrecise([1, 2]), noMath.sumPrecise(1, 2).
7.3 Compatibilidad
Math.sumPrecise funciona en las versiones actuales de Chrome, Edge, Firefox y Safari, pero Node.js 24 todavía no la incluye. Comprueba siempre su existencia antes de usarla:
const importes = [0.1, 0.2, 0.3];
const total = typeof Math.sumPrecise === 'function'
? Math.sumPrecise(importes)
: importes.reduce((acumulado, n) => acumulado + n, 0); // alternativa clásica
console.log(total); // 0.6 en navegadores actuales; 0.6000000000000001 en Node.js 24
💡 Consejo: Para dinero, la técnica profesional clásica sigue siendo trabajar en céntimos enteros (
1999en lugar de19.99) y dividir entre 100 solo para mostrar. Los enteros hastaMAX_SAFE_INTEGERse suman sin ningún error. Si necesitasMath.sumPreciseen entornos que no la tienen, existen polyfills en npm (por ejemplo, el paquetemath.sumprecise).
// Céntimos enteros: sin errores de redondeo
const preciosEnCentimos = [1999, 501, 10, 20];
const totalCentimos = preciosEnCentimos.reduce((a, b) => a + b, 0);
console.log(totalCentimos); // 2530
console.log((totalCentimos / 100).toFixed(2)); // 25.30
8. BigInt: enteros sin límite
Cuando necesitas enteros más grandes que MAX_SAFE_INTEGER (criptografía, IDs enormes, cálculos científicos), usa BigInt (ES2020). Se escribe añadiendo una n al final del número o con la función BigInt():
const grande = 9007199254740993n;
console.log(grande); // 9007199254740993n ✅ exacto
console.log(2n ** 64n); // 18446744073709551616n
console.log(10n ** 20n); // 100000000000000000000n
console.log(BigInt(Number.MAX_SAFE_INTEGER) + 2n); // 9007199254740993n
console.log(typeof 5n); // bigint
Reglas importantes:
// 1. No se pueden mezclar BigInt y number en operaciones
console.log(5n + 1); // ❌ TypeError: Cannot mix BigInt and other types, use explicit conversions
console.log(5n + BigInt(1)); // 6n ✅
// 2. La división es entera (descarta decimales)
console.log(7n / 2n); // 3n
// 3. Comparar sí se puede
console.log(5n == 5); // true (mismo valor matemático)
console.log(5n === 5); // false (tipos distintos)
console.log(10n > 5); // true
💡 Consejo: BigInt es más lento que
numbery no funciona conMath(Math.sqrt(4n)da error). Úsalo solo cuando realmente necesites enteros enormes.
9. Texto (string) y symbol
9.1 Cadenas de texto
Un string es una secuencia de caracteres. Hay tres formas de escribirlo:
const simples = 'Hola'; // comillas simples (las que usamos en el curso)
const dobles = "Hola"; // comillas dobles (equivalentes)
const plantilla = `Hola, ${simples}`; // comillas invertidas: template literal
Las plantillas (template literals) permiten insertar expresiones con ${...} y escribir varias líneas:
const producto = 'Teclado';
const precio = 29.99;
const unidades = 2;
console.log(`Has comprado ${unidades} x ${producto}: ${precio * unidades} €`);
// Has comprado 2 x Teclado: 59.98 €
Operaciones básicas:
const saludo = 'Hola';
console.log(saludo.length); // 4 (número de caracteres)
console.log(saludo[0]); // H (primer carácter; los índices empiezan en 0)
console.log(saludo.at(-1)); // a (último carácter)
console.log(saludo.toUpperCase()); // HOLA
console.log(saludo + ' mundo'); // Hola mundo (concatenación con +)
console.log('It\'s'); // It's (\' escapa la comilla)
console.log('Línea 1\nLínea 2'); // \n = salto de línea
En el módulo 08 verás decenas de métodos de cadenas.
9.2 symbol: identificadores únicos
Un Symbol es un valor único e irrepetible, incluso si tiene la misma descripción:
const id1 = Symbol('id');
const id2 = Symbol('id');
console.log(id1 === id2); // false: cada Symbol es único
console.log(id1.description); // id
Se usan sobre todo como claves “ocultas” de objetos y para personalizar el comportamiento del lenguaje (Symbol.iterator, que verás en el módulo 11). Como principiante, rara vez crearás los tuyos.
10. boolean y valores truthy / falsy
Un boolean solo puede valer true (verdadero) o false (falso). Es el resultado de las comparaciones y la base de las decisiones:
const esMayorDeEdad = 20 >= 18;
console.log(esMayorDeEdad); // true
10.1 Truthy y falsy
Cuando JavaScript necesita un booleano (por ejemplo, en un if) y le das otro tipo de valor, lo convierte automáticamente. Los valores que se convierten en false se llaman falsy; todos los demás son truthy.
⚠️ Importante: Solo hay ocho valores falsy. Apréndetelos: todo lo que no esté en esta lista es truthy.
| Valor falsy | Tipo |
|---|---|
false | boolean |
0 | number |
-0 | number |
0n | bigint |
'' (cadena vacía) | string |
null | null |
undefined | undefined |
NaN | number |
Valores truthy que sorprenden:
| Valor | ¿Truthy? | Por qué sorprende |
|---|---|---|
'0' | Sí | Es un texto con un carácter, no el número 0 |
'false' | Sí | Es un texto no vacío |
' ' | Sí | Un espacio no es una cadena vacía |
[] | Sí | Un array vacío es un objeto, y todos los objetos son truthy |
{} | Sí | Ídem |
-1 | Sí | Cualquier número distinto de 0 y NaN |
const nombre = '';
if (nombre) {
console.log(`Hola, ${nombre}`);
} else {
console.log('No has escrito tu nombre'); // ← se ejecuta esto ('' es falsy)
}
const carrito = [];
if (carrito) {
console.log('Esto se ejecuta aunque el carrito esté vacío'); // [] es truthy
}
if (carrito.length > 0) {
console.log('Esto NO se ejecuta'); // ✅ la comprobación correcta
}
11. null frente a undefined
Los dos representan “ausencia de valor”, pero con intención distinta:
undefined | null | |
|---|---|---|
| Significado | “Todavía no tiene valor” | “No tiene valor, a propósito” |
| Quién lo pone | Normalmente JavaScript | Normalmente el programador |
typeof | 'undefined' | 'object' (error histórico) |
| Convertido a número | NaN | 0 |
Dónde aparece undefined automáticamente:
let sinValor;
console.log(sinValor); // undefined: variable sin inicializar
const persona = { nombre: 'Ana' };
console.log(persona.edad); // undefined: propiedad que no existe
function saludar(nombre) {
console.log(nombre);
}
saludar(); // undefined: parámetro que no se pasó
function nada() {}
console.log(nada()); // undefined: función sin return
Cuándo usar null:
let usuarioConectado = null; // aún no hay nadie conectado, y lo decimos explícitamente
// ...más tarde
usuarioConectado = { nombre: 'Ana' };
// ...al cerrar sesión
usuarioConectado = null;
💡 Consejo: Una convención común es: deja que
undefinedlo ponga JavaScript y usanullcuando tú quieras indicar “vacío a propósito”. Para comprobar “ninullniundefined” verás en el módulo 03 el operador??, pensado justo para esto.
12. Conversiones explícitas
Muchas veces recibes datos del tipo equivocado: lo que escribe el usuario en un formulario o en prompt siempre es texto, aunque escriba un número. Convertir de forma explícita (diciéndolo tú) es la forma segura de trabajar.
12.1 A número: Number(), parseInt(), parseFloat()
console.log(Number('42')); // 42
console.log(Number('3.5')); // 3.5
console.log(Number('')); // 0 ← ¡cuidado! la cadena vacía es 0
console.log(Number(' 12 ')); // 12 (ignora espacios alrededor)
console.log(Number('12px')); // NaN (todo el texto debe ser un número)
console.log(Number(true)); // 1
console.log(Number(false)); // 0
console.log(Number(null)); // 0
console.log(Number(undefined)); // NaN
parseInt y parseFloat leen desde el principio del texto y se detienen al encontrar algo que no es un número:
console.log(parseInt('12px')); // 12
console.log(parseInt('3.99')); // 3 (solo la parte entera)
console.log(parseFloat('3.99€')); // 3.99
console.log(parseInt('px12')); // NaN (no empieza por número)
console.log(parseInt('ff', 16)); // 255 (segundo argumento: la base)
console.log(parseInt('101', 2)); // 5
console.log(parseInt('')); // NaN (a diferencia de Number(''))
| Entrada | Number() | parseInt() | parseFloat() |
|---|---|---|---|
'42' | 42 | 42 | 42 |
'3.99' | 3.99 | 3 | 3.99 |
'12px' | NaN | 12 | 12 |
'' | 0 | NaN | NaN |
' 7 ' | 7 | 7 | 7 |
null | 0 | NaN | NaN |
💡 Consejo: Usa
Number()cuando el texto deba ser entero un número (por ejemplo, un campo de formulario) y comprueba después conNumber.isNaN(). UsaparseInt/parseFloatcuando quieras extraer el número del principio de un texto, como'12px'. ConparseIntindica siempre la base:parseInt(texto, 10).
12.2 A texto: String() y toString()
console.log(String(123)); // '123'
console.log(String(true)); // 'true'
console.log(String(null)); // 'null'
console.log(String(undefined)); // 'undefined'
console.log(String([1, 2])); // '1,2'
console.log((255).toString(16)); // 'ff' (toString admite base en números)
console.log((5).toString(2)); // '101'
⚠️ Importante:
null.toString()yundefined.toString()dan error, porquenullyundefinedno tienen métodos.String(valor)funciona con cualquier valor.
12.3 A booleano: Boolean()
console.log(Boolean(1)); // true
console.log(Boolean(0)); // false
console.log(Boolean('hola')); // true
console.log(Boolean('')); // false
console.log(Boolean('false')); // true ← texto no vacío
12.4 Atajos: + unario y !!
En código profesional verás estos atajos:
console.log(+'42'); // 42 (el + delante convierte a número, como Number())
console.log(+''); // 0
console.log(+true); // 1
console.log(!!'hola'); // true (doble negación: convierte a booleano, como Boolean())
console.log(!!0); // false
💡 Consejo:
Number(x)yBoolean(x)son más legibles para un principiante;+xy!!xson más cortos. Ambos son correctos: lo importante es que la conversión sea intencionada.
13. Coerción implícita: la tabla de sorpresas
📘 Concepto: La coerción implícita es la conversión automática que hace JavaScript cuando mezclas tipos en una operación. La regla que explica casi todo: el operador
+con un texto concatena; el resto de operadores aritméticos (-,*,/,%) convierten a número.
| Expresión | Resultado | Explicación |
|---|---|---|
'5' + 1 | '51' | Hay un texto: + concatena |
'5' - 1 | 4 | - solo funciona con números: convierte '5' |
'5' * '2' | 10 | Ambos se convierten a número |
'10' / '2' | 5 | Ídem |
1 + 2 + '3' | '33' | De izquierda a derecha: 1 + 2 = 3, luego 3 + '3' |
'1' + 2 + 3 | '123' | '1' + 2 = '12', luego '12' + 3 |
true + 1 | 2 | true se convierte en 1 |
null + 1 | 1 | null se convierte en 0 |
undefined + 1 | NaN | undefined se convierte en NaN |
[] + [] | '' | Los arrays se convierten en texto vacío |
[] + {} | '[object Object]' | El objeto se convierte en ese texto |
'b' + 'a' + +'a' + 'a' | 'baNaNa' | +'a' es NaN, que se concatena como texto |
Number([]) | 0 | [] → '' → 0 |
Number([5]) | 5 | [5] → '5' → 5 |
Number({}) | NaN | {} → '[object Object]' → NaN |
Un error real y muy común:
// En el navegador, prompt() SIEMPRE devuelve texto
const a = prompt('Primer número'); // el usuario escribe 2
const b = prompt('Segundo número'); // el usuario escribe 3
console.log(a + b); // '23' ❌ concatena
// ✅ Convertimos explícitamente
console.log(Number(a) + Number(b)); // 5
⚠️ Importante: No hace falta memorizar la tabla. Hace falta evitar depender de ella: convierte explícitamente con
Number(),String()oBoolean()y compara con===(módulo 03). Así la coerción implícita nunca te sorprenderá.
14. Inmutabilidad de primitivos, valor y referencia
14.1 Los primitivos son inmutables
Un valor primitivo no se puede modificar: solo se puede sustituir por otro.
const saludo = 'hola';
saludo[0] = 'H'; // ❌ en modo estricto: TypeError: Cannot assign to read only property '0' of string 'hola'
// Los métodos NO modifican el original: devuelven un valor nuevo
let nombre = 'ana';
nombre.toUpperCase();
console.log(nombre); // ana (sin cambios)
nombre = nombre.toUpperCase(); // guardamos el nuevo valor
console.log(nombre); // ANA
14.2 Copiar por valor (primitivos)
Cuando asignas un primitivo a otra variable, se copia el valor. Son dos cajas independientes:
let x = 10;
let y = x; // y recibe una COPIA del 10
y = 20;
console.log(x); // 10 (x no cambia)
console.log(y); // 20
14.3 Copiar por referencia (objetos) — adelanto
Con los objetos (y arrays) es distinto: la variable no guarda el objeto, sino una referencia (una “dirección”) al lugar de la memoria donde está. Al asignarla a otra variable, se copia la dirección, no el objeto:
const a = { valor: 1 };
const b = a; // b apunta al MISMO objeto que a
b.valor = 99;
console.log(a.valor); // 99 ← ¡a también ha cambiado!
📘 Concepto: Es como compartir la dirección de una casa: si le das a alguien tu dirección y pinta la puerta de rojo, cuando tú llegues también verás la puerta roja. Solo hay una casa. En el módulo 07 aprenderás a hacer copias reales con spread y
structuredClone().
flowchart LR
subgraph Primitivos
X["x: 10"]
Y["y: 20"]
end
subgraph Objetos
A["a"] --> O["{ valor: 99 }"]
B["b"] --> O
end
15. La zona muerta temporal (TDZ)
Vimos que var sube al principio de su ámbito con valor undefined. let y const también suben, pero sin inicializar: desde el comienzo del bloque hasta la línea donde se declaran, la variable está en la zona muerta temporal (Temporal Dead Zone, TDZ). Usarla ahí produce un error:
console.log(producto); // ❌ ReferenceError: Cannot access 'producto' before initialization
const producto = 'Teclado';
console.log(producto); // Teclado
{
// ── inicio de la TDZ de "total" ──
// console.log(total); ❌ ReferenceError
// total = 5; ❌ ReferenceError
let total = 0; // ── fin de la TDZ ──
total = 5; // ✅
console.log(total); // 5
}
📘 Concepto: Fíjate en la diferencia entre los mensajes:
Cannot access 'producto' before initialization(la variable existe pero estás en su TDZ) yproducto is not defined(la variable no existe en absoluto). La TDZ es una ventaja: convierte un error silencioso devaren un error claro.
💡 Consejo: La forma de no tropezar nunca con la TDZ es simple: declara las variables al principio del bloque donde las usas y antes de leerlas.
16. Ejercicios
Ejercicio 1: Ficha personal
Crea un fichero ficha.mjs que declare con const o let (decide tú cuál en cada caso y justifícalo en un comentario) tu nombre, tu año de nacimiento, el año actual, tu ciudad y si tienes carné de conducir. Calcula tu edad y muestra con una plantilla de texto: Me llamo Ana, tengo 28 años, vivo en Madrid y tengo carné: true.
Ejercicio 2: Detective de tipos
Sin ejecutar, escribe qué devuelve typeof para cada valor. Después compruébalo en la consola y explica los que hayas fallado: 42, '42', 42n, null, undefined, [], {}, NaN, function () {}, Symbol(), true, typeof 42.
Ejercicio 3: Conversor seguro
Escribe un programa con un array de textos ['42', '3.14', '', '12px', 'abc', ' 7 ', '0x10']. Para cada uno, muestra en una tabla (console.table con un array de objetos) el resultado de Number(), parseInt(texto, 10) y parseFloat(), y si el resultado de Number() es un número válido según Number.isNaN().
Ejercicio 4: Truthy o falsy
Crea un array con estos valores: 0, '0', '', ' ', null, undefined, [], {}, NaN, -1, 0n, 'false'. Recórrelo (puedes usar for (const valor of valores), que verás en el módulo 04) y muestra para cada uno si es truthy o falsy usando Boolean(). Compara el resultado con la tabla de la sección 10.
Ejercicio 5: El ticket de la compra
Tienes estos precios: 19.99, 5.01, 0.10, 0.20, 3.33. Calcula el total de tres formas: (a) sumando los number directamente, (b) convirtiendo cada precio a céntimos enteros con Math.round(precio * 100), sumando y dividiendo entre 100, y (c) con Math.sumPrecise si está disponible (usa detección de características). Muestra los tres resultados y el total formateado con toFixed(2).
Ejercicio 6: Números enormes
Calcula 2 ** 60 con number y con BigInt (2n ** 60n) y compara los resultados. Después calcula el factorial de 25 (1 × 2 × 3 × ... × 25) multiplicando a mano (puedes escribir todas las multiplicaciones) primero con number y luego con BigInt. ¿Cuál es exacto? Comprueba con Number.isSafeInteger el resultado con number.
Ejercicio 7: Caza los errores
Este código tiene cinco problemas (algunos dan error y otros dan resultados incorrectos). Encuéntralos, explícalos y corrígelos:
console.log(descuento);
const descuento = 0.1;
var total = '100';
const final = total + total * descuento;
const IVA;
IVA = 0.21;
if (final == NaN) {
console.log('Error en el cálculo');
}
Resumen
| Concepto | Sintaxis / idea clave |
|---|---|
| Variable reasignable | let contador = 0; |
| Constante (por defecto) | const IVA = 0.21; (el objeto interior sí puede cambiar) |
var | Ámbito de función, hoisting con undefined, redeclarable: no usar |
| Nombres | camelCase; PascalCase para clases; UPPER_SNAKE_CASE para configuración |
| Primitivos | number, string, boolean, undefined, null, bigint, symbol |
| Tipo de un valor | typeof x (ojo: typeof null === 'object') |
| ¿Es array? | Array.isArray(x) |
| ¿Es NaN? | Number.isNaN(x) |
| ¿Es entero? | Number.isInteger(x), Number.isSafeInteger(x) |
| Separadores numéricos | 1_000_000 |
| Mostrar decimales | n.toFixed(2) (devuelve texto) |
| Suma precisa (ES2026) | Math.sumPrecise([0.1, 0.2, 0.3]) → 0.6 |
| Enteros enormes | 123n, BigInt('123') (no mezclar con number) |
| Falsy | false, 0, -0, 0n, '', null, undefined, NaN |
| Conversión a número | Number(x), parseInt(x, 10), parseFloat(x), +x |
| Conversión a texto / booleano | String(x) / Boolean(x), !!x |
Coerción con + | Si hay un texto, concatena |
| Valor frente a referencia | Primitivos se copian; objetos se comparten |
| TDZ | let/const no se pueden usar antes de su declaración |