Tipos de datos en JavaScript
JavaScript es un lenguaje dinámico y flexible, pero entender sus tipos de datos es esencial para escribir código fiable, mantenible y con buen rendimiento. En este artículo encontrarás una descripción exhaustiva de cada tipo, ejemplos prácticos, comparativas con otros lenguajes y recomendaciones de seguridad y optimización.
1. ¿Por qué importa el tipo de dato?
El motor JavaScript (V8, SpiderMonkey, JavaScriptCore, etc.) interpreta el código en tiempo de ejecución. Conocer los tipos permite:
- Prevenir errores de runtime y bugs sutiles.
- Optimizar el uso de memoria y el rendimiento del JIT.
- Aplicar correctamente técnicas de sanitización y evitar vulnerabilidades como injection o prototype pollution.
2. Clasificación de los tipos
Tipos primitivos
Son inmutables y se copian por valor. JavaScript define seis primitivos:
- Number – incluye
NaNyInfinity. - BigInt – enteros de precisión arbitraria.
- String – secuencias de caracteres Unicode.
- Boolean –
trueofalse. - Symbol – identificadores únicos.
- undefined – valor por defecto de variables no inicializadas.
- null – referencia nula (intencional).
Ejemplos rápidos
let age = 27; // Number
let big = 12345678901234567890n; // BigInt
let name = "Ana"; // String
let isReady = true; // Boolean
let id = Symbol('uid'); // Symbol
let notSet; // undefined
let empty = null; // null
Tipos de referencia (objetos)
Se almacenan en el heap y se copian por referencia. Incluyen:
- Object – el tipo base para estructuras de datos.
- Array – lista ordenada de valores.
- Function – objetos invocables.
- Date, RegExp, Map, Set, WeakMap, WeakSet – estructuras especializadas.
- TypedArray – vistas de buffers binarios.
Ejemplo de creación y mutabilidad
let user = { name: "Luis", age: 30 };
user.age = 31; // Mutación en el mismo objeto
let numbers = [1, 2, 3];
numbers.push(4); // Modifica la referencia
function greet() { console.log('Hello'); }
// greet es un objeto Function y, como tal, tiene propiedades
Nota de seguridad: nunca confíes en datos externos para crear o modificar prototipos (Object.prototype), pues podrías abrir la puerta a prototype pollution.
3. Coerción de tipos y operadores de comparación
JavaScript efectúa coerción implícita en muchos contextos. Conocer cuándo ocurre evita sorpresas:
==vs===: la primera convierte los operandos, la segunda no.- Operadores aritméticos convierten a
Number(excepto+que concatena strings).
Ejemplos ilustrativos
0 == false // true (coerción a Number)
0 === false // false (tipo diferente)
"5" + 1 // "51" (concatenación)
"5" - 1 // 4 (coerción a Number)
null == undefined // true
null === undefined // false
Para código robusto, prefiere siempre === y !==. Cuando necesites conversión explícita, usa Number(), String(), Boolean() o BigInt().
4. Detectar tipos en tiempo de ejecución
typeof
Devuelve una cadena que describe el tipo primitivo o "object"/"function" para referencias.
typeof 42 // "number"
typeof "hi" // "string"
typeof true // "boolean"
typeof Symbol() // "symbol"
typeof undefined // "undefined"
typeof null // "object" (quirk histórico)
typeof [] // "object"
typeof {} // "object"
typeof (()=>{}) // "function"
instanceof
Comprueba la cadena de prototipos. Es útil para objetos y arrays.
[] instanceof Array // true
{} instanceof Object // true
new Date() instanceof Date // true
function Foo() {}
new Foo() instanceof Foo // true
Para valores primitivos instanceof siempre devuelve false. En entornos con múltiples contextos (iframes, workers) instanceof puede fallar; en esos casos Object.prototype.toString.call(value) es más fiable.
5. Mejores prácticas al trabajar con tipos
- Usa
constsiempre que sea posible. Evita reasignaciones inesperadas. - Valida entradas externas. Emplea
typeofyArray.isArray()antes de operar. - Prefiere estructuras inmutables. Usa
Object.freeze()o librerías como Immer para evitar mutaciones accidentales. - Tipado estático opcional. Integra TypeScript o Flow para detectar errores de tipo en tiempo de compilación.
- Evita la comparación con
nullyundefinedusando el operador de coalescencia nula (??).
Ejemplo de validación robusta
function processUser(input) {
if (typeof input !== "object" || input === null) {
throw new TypeError("Se esperaba un objeto de usuario");
}
const { name, age } = input;
if (typeof name !== "string" || name.trim() === "") {
throw new TypeError("'name' debe ser una cadena no vacía");
}
if (!Number.isInteger(age) || age < 0) {
throw new RangeError("'age' debe ser un entero positivo");
}
// lógica del negocio
}
6. Impacto de los tipos en rendimiento
Los motores modernos optimizan los hidden classes y la monomorfización de funciones. Mantener la consistencia de tipos ayuda al JIT a generar código más rápido.
- Evita mezclar tipos en arrays (ej.
[1, "a", true]) si el rendimiento es crítico. - Prefiere
Number.isFinite()yNumber.isNaN()sobreisNaN()para evitar conversiones implícitas. - Cuando trabajes con datos binarios, usa
ArrayBufferyTypedArray(Uint8Array, Float64Array, etc.) para evitar sobrecarga de objetos.
Benchmark rápido (Node.js)
const { performance } = require('perf_hooks');
function sumNumbers(arr) {
let total = 0;
for (let i = 0; i < arr.length; i++) total += arr[i];
return total;
}
function sumMixed(arr) {
let total = 0;
for (let i = 0; i < arr.length; i++) total += +arr[i]; // coerción
return total;
}
const size = 1e6;
const nums = Array.from({length:size}, (_,i)=>i);
const mixed = nums.map(v=> (v%2===0? v : `${v}`));
let t0 = performance.now();
sumNumbers(nums);
let t1 = performance.now();
sumMixed(mixed);
let t2 = performance.now();
console.log('Solo números:', (t1-t0).toFixed(2), 'ms');
console.log('Mezcla + coerción:', (t2-t1).toFixed(2), 'ms');
En la mayoría de los navegadores, la versión “solo números” será significativamente más rápida.
7. Seguridad relacionada con tipos
- Validación de tipos antes de usar datos en
eval,Functiono plantillas de cadena. Unstringinesperado puede ejecutar código arbitrario. - Evita el uso de
Object.prototype.__proto__oObject.setPrototypeOfen datos procedentes del cliente. Puede resultar en prototype pollution. - Sanitiza los valores que se convierten a
Numberantes de usarlos en consultas a bases de datos.NaNoInfinitypueden romper lógicas de paginación.
Ejemplo de mitigación de prototype pollution
function safeMerge(target, source) {
const prohibited = ["__proto__", "prototype", "constructor"];
for (const key of Object.keys(source)) {
if (prohibited.includes(key)) continue; // descarta keys peligrosas
target[key] = source[key];
}
return target;
}
8. Compatibilidad entre navegadores y Node.js
Los tipos básicos son universales, pero algunos como BigInt y globalThis solo están disponibles a partir de:
- Chrome 67+, Firefox 68+, Safari 14+, Edge 79+ (Chromium).
- Node.js 10.4+ (experimental) y 12+ (estable).
Para proyectos que deben soportar navegadores antiguos, utiliza Babel con el preset @babel/preset-env y polyfills de core-js.
9. Tabla comparativa de tipos (Resumen)
| Tipo | Categoría | Mutabilidad | Operaciones comunes | Ejemplo |
|---|---|---|---|---|
| Number | Primitivo | Inmutable | +, -, *, /, % | 42 |
| BigInt | Primitivo | Inmutable | +, -, *, / (sin decimales) | 123n |
| String | Primitivo | Inmutable | concatenación, slice, replace | "hola" |
| Boolean | Primitivo | Inmutable | &&, ||, ! | true |
| Symbol | Primitivo | Inmutable | descripción, uso como claves | Symbol('id') |
| undefined | Primitivo | Inmutable | comparaciones, typeof | let x; |
| null | Primitivo | Inmutable | comparaciones, asignación | let y = null; |
| Object | Referencia | Mutable | propiedad, spread, Object.assign | {a:1} |
| Array | Referencia | Mutable | push, map, filter, reduce | [1,2,3] |
| Function | Referencia | Mutable (propiedades) | call, apply, bind | function(){} |
10. Conclusión
Dominar los tipos de datos en JavaScript es la base para escribir código seguro, rápido y fácil de mantener. Aplica las buenas prácticas aquí descritas, considera integrar TypeScript para un tipado estático y mantente al día con las mejoras de los motores JavaScript.
Tipos de datos en JavaScript: Conceptos, ejemplos y buenas prácticas