Tipos JavaScript
Entiende cómo funciona el sistema de tipos en JavaScript: desde los siete tipos primitivos inmutables hasta los tipos de referencia. Aprende a diferenciar entre valores y referencias, cómo actúan al ser pasados a funciones y qué errores evitar.
JavaScript tiene un sistema de tipos dinámico. Esto significa que no necesitas declarar de antemano el tipo de una variable: se determina automáticamente en tiempo de ejecución, según el valor que se le asigne.
Comprender los tipos es clave para evitar errores invisibles, entender cómo se comportan tus datos y escribir código que pase el test del tiempo.
Tipos primitivos
En JavaScript existen 7 tipos primitivos, que son inmutables:
-
string
— Texto. -
number
— Números, tanto enteros como decimales. -
boolean
—true
ofalse
. -
null
— Ausencia intencionada de valor. -
undefined
— Valor no asignado. -
symbol
— Identificadores únicos. -
bigint
— Números enteros muy grandes.
let name = 'Alice'; // string
let age = 30; // number
let isLoggedIn = true; // boolean
let notSet = null; // null
let notDefined; // undefined
let id = Symbol('unique'); // symbol
let large = 12345678901234567890n; // bigint
Tipos no primitivos
Además de los tipos primitivos, JavaScript tiene tipos no primitivos, también llamados tipos de referencia. Estos son mutables y se almacenan en memoria como referencias, no como valores directos:
-
object
— Estructura clave-valor genérica. -
array
— Lista ordenada de elementos (técnicamente, un tipo especial de objeto). -
function
— Bloques de código reutilizables (también objetos de alto nivel).
Estos tipos pueden mutar internamente aunque la variable que los contiene sea const
, porque lo que se mantiene constante es la referencia, no el contenido.
¿Qué significa que un tipo sea inmutable?
Un tipo inmutable es aquel cuyo valor no puede cambiarse una vez creado.
En JavaScript, los tipos primitivos (string
, number
, boolean
, null
, undefined
, symbol
, bigint
) son inmutables. Esto no significa que no puedas reasignar una variable con un nuevo valor, sino que el valor en sí no puede ser modificado internamente.
Por ejemplo:
let message = 'Hello';
message[0] = 'Y';
console.log(message); // "Hello" (no change)
Aunque intentamos cambiar el primer carácter de la cadena, el valor no se modifica. Las cadenas son inmutables: si quieres otro valor, tienes que crear una nueva cadena.
En contraste, los objetos, arrays y funciones son mutables:
let user = { name: 'Alice' };
user.name = 'Bob';
console.log(user.name); // "Bob"
Aquí, el objeto user
sí cambia internamente, porque es un tipo de referencia mutable.
Resumen:
- Inmutable: no puedes modificar el valor original (primitivos).
- Mutable: puedes modificar el contenido del valor (objetos, arrays, funciones), pero no cambiar la referencia.
typeof
y verificación de tipos
Puedes usar el operador typeof
para comprobar el tipo de un valor:
const name = "Alice";
const age = 33;
console.log(typeof name); // "string"
console.log(typeof age); // "number"
console.log(typeof null); // "object" (WTF?)
El caso de null
devolviendo "object"
es un bug histórico en JavaScript. Sigue ocurriendo hoy por retrocompatibilidad.
Tipos de referencia
Los tipos que no son primitivos son tipos de referencia: objetos, arrays, funciones...
let user = { name: 'Alice' }; // object
let items = [1, 2, 3]; // array
function greet() { return 'Hi'; } // function
Todos estos tipos se almacenan por referencia, no por valor. Es decir, si los asignas o pasas a una función, se comparte la referencia, no se copia el valor.
let a = { value: 10 };
let b = a;
b.value = 20;
console.log(a.value); // 20
Esto significa que cuando creas una variable que contiene, por ejemplo, un array, lo que almacenas no es el array directamente, sino una referencia en memoria al array.
const items = [1, 2, 3];
const moreItems = items;
moreItems.push(4);
console.log(items); // [1, 2, 3, 4]
console.log(moreItems); // [1, 2, 3, 4]
Aunque solo modificaste moreItems
, también cambió items
, porque ambas variables apuntan a la misma dirección de memoria. Esto no ocurre con los primitivos.
Diferencia al pasar como argumento
Cuando pasas un valor a una función, JavaScript siempre pasa una copia del valor. Pero la diferencia es:
- Si el valor es primitivo, se pasa una copia del valor.
- Si es referencia (objeto, array, función), se pasa una copia de la referencia.
Ejemplo con primitivo:
let score = 10;
function update(val) {
val = val + 1;
}
update(score);
console.log(score); // 10 (no change)
Ejemplo con referencia:
let user = { name: 'Alice' };
function rename(obj) {
obj.name = 'Bob';
}
rename(user);
console.log(user.name); // "Bob" (change)
Resumen práctico
Tipo de dato | ¿Inmutable? | ¿Pasa valor o referencia? |
---|---|---|
string |
Sí | Valor |
number |
Sí | Valor |
object |
No | Referencia |
array |
No | Referencia |
function |
No | Referencia |
- Con los primitivos estás trabajando con copias.
- Con los tipos de referencia estás trabajando con el original (a través de un puntero).
Esto tiene implicaciones en rendimiento, mutabilidad y diseño de funciones puras, que veremos más adelante.
Conversión de tipos
JavaScript realiza conversiones implícitas con frecuencia, lo que puede ser fuente de errores.
let result = '5' + 3; // "53" (string)
let sum = '5' - 1; // 4 (number)
let check = !!'hello'; // true (boolean)
Estas conversiones se conocen como coerción de tipos. Conviene entenderlas, pero no confiar ciegamente en ellas.
Para evitar errores, puedes forzar conversiones explícitas:
let num = Number('42'); // 42
let str = String(123); // "123"
let bool = Boolean(0); // false
Comparaciones y coerción
Cuando usas ==
(igualdad laxa), JavaScript compara con coerción. Es decir, convierte tipos si es necesario.
'5' == 5 // true
null == undefined // true
false == 0 // true
Con ===
(igualdad estricta), no hay conversión implícita: compara valor y tipo.
'5' === 5 // false
null === undefined // false
false === 0 // false
Por eso, se recomienda usar siempre ===
y !==
salvo que tengas una razón muy clara y sepas lo que estás haciendo.
Valores falsy y truthy
Ciertos valores se comportan como false
en contextos booleanos. Estos son los valores falsy:
-
false
-
0
-
''
(string vacía) -
null
-
undefined
-
NaN
(not a number)
Todo lo demás es truthy (se evalúa como true
).
if ('') {
console.log('Will not run');
}
if ('hello') {
console.log('Runs'); // this runs
}
Errores comunes
Creer que null
y undefined
son lo mismo
Son distintos. undefined
indica ausencia de valor asignado. null
es una ausencia intencionada.
let x;
console.log(x); // undefined
let y = null;
console.log(y); // null
Usar ==
en vez de ===
sin entender la diferencia
Evita comparaciones que puedan dar resultados inesperados:
'' == 0 // true
false == '0' // true
No comprobar tipos de retorno
Algunas funciones pueden devolver undefined
, null
, o lanzar errores si no compruebas el tipo esperado. A esto se le llama “programación defensiva”.
Resumen práctico
- Usa
typeof
para inspeccionar tipos. - Usa
===
siempre que compares valores. - Conoce los falsy y truthy.
- No confíes en la coerción implícita.
- Aprende a distinguir entre primitivos (valor) y referencias (punteros).
Verificación de comprensión
- ¿Cuál es la diferencia entre
null
yundefined
?. - ¿Por qué
'5' == 5
estrue
, pero'5' === 5
esfalse
?. - ¿Qué significa que un valor sea truthy?.
- ¿Qué devuelve
typeof null
y por qué es un caso especial?.
Si alguna de estas preguntas te cuesta, vuelve a repasar la sección correspondiente.