Guía de JavaScript: De 0 a 100
¿Qué es JavaScript?
JavaScript es un lenguaje de programación interpretado, de tipado dinámico y multiparadigma, creado por Brendan Eich en 1995 en Netscape. Originalmente diseñado para añadir interactividad al navegador, hoy es uno de los lenguajes más ubicuos del mundo.
Nacido como Mocha, luego LiveScript y finalmente JavaScript, en 1997 se estandarizó como ECMAScript (ES). Desde ES6 (2015) el lenguaje ha evolucionado enormemente con clases, promesas, módulos, y más.
¿Dónde se usa?
- Frontend web: React, Vue, Svelte, Angular — el único lenguaje nativo del navegador
- Backend: Node.js (Express, Fastify, NestJS), Deno, Bun — JavaScript en el servidor
- Aplicaciones de escritorio: Electron (VS Code, Slack, Discord, Figma), Tauri
- Aplicaciones móviles: React Native, Ionic, NativeScript, Expo
- Bases de datos: MongoDB (aggregation pipeline), Firebase (Cloud Functions), CockroachDB
- IoT: Johnny-Five, Espruino, Moddable SDK
- CLI / herramientas: npm, yarn, eslint, vite, webpack, turborepo
- Juegos: Phaser, Three.js, Babylon.js, PlayCanvas
¿Quién lo usa? Google (GMail, Maps, YouTube), Netflix (UI + Express backend), Facebook/Meta (React, Jest), Microsoft (VS Code, Teams, Edge), PayPal (Node.js backend), LinkedIn (backbone de la app), Figma (editor en WebAssembly + JS), MongoDB (shell y drivers).
Fuente: MDN — About JavaScript, ECMAScript spec, Node.js — About
Prerrequisitos
Antes de empezar, necesitas:
- Conocimientos básicos de terminal: navegar directorios, ejecutar comandos, usar un editor de texto
- El lenguaje/herramienta instalado en tu sistema (sigue la sección de Instalación si aún no lo tienes)
Si no cumples algún requisito, no te preocupes: cada sección te guiará paso a paso.
¿Cómo empezar? Instalación en todos los entornos
Navegador (el intérprete más básico)
Abre Chrome, Edge o Firefox → F12 → pestaña Console. Ya tienes un REPL de JavaScript. Escribe console.log("Hola mundo").
Node.js (estándar para backend y herramientas)
# Windows — descargar de https://nodejs.org/ (LTS)
# O con winget:
winget install OpenJS.NodeJS.LTS
# macOS
brew install node@22
# Linux (Debian/Ubuntu)
curl -fsSL https://deb.nodesource.com/setup_22.x | sudo -E bash -
sudo apt install nodejs
# Verificar
node --version # v22.x
npm --version # 10.x
nvm (gestor de versiones de Node)
curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.40.1/install.sh | bash
# Recargar shell o:
export NVM_DIR="$HOME/.nvm"
[ -s "$NVM_DIR/nvm.sh" ] && \. "$NVM_DIR/nvm.sh"
nvm install 22 # Última LTS
nvm install 20 # LTS anterior
nvm install 18 # Aún con soporte
nvm alias default 22
nvm use 22
Docker
FROM node:22-alpine
WORKDIR /app
COPY package.json package-lock.json ./
RUN npm ci --only=production
COPY . .
EXPOSE 3000
CMD ["node", "server.js"]
Usa node:22-alpine (~130 MB). Para desarrollo, node:22-slim incluye herramientas de compilación.
Bun (alternativa ultrarrápida)
curl -fsSL https://bun.sh/install | bash
bun --version
bun init
bun run index.ts # Bun ejecuta TypeScript nativamente
Bun es un runtime todo-en-uno (intérprete, bundler, package manager, test runner). Compatible con la mayoría de APIs de Node.js.
Deno (seguro por defecto, TypeScript nativo)
curl -fsSL https://deno.land/install.sh | sh
deno --version
deno run https://deno.land/std/examples/welcome.ts
Deno implementa web APIs estándar (fetch, WebSocket) de forma nativa. No usa package.json — importa desde URLs.
Alias útiles para el día a día
Añade a tu ~/.bashrc o ~/.zshrc:
alias node='node --experimental-strip-types' # Ejecuta TS sin ts-node
alias nrd='npm run dev'
alias nrb='npm run build'
alias nrt='npm test'
alias ni='npm install'
alias nrs='npm run start'
alias ncu='npx npm-check-updates -u' # Actualiza dependencias
alias pn='pnpm'
alias b='bun'
Fuente: nvm GitHub, Node.js Docker images, Bun docs, Deno docs
Escala de aprendizaje: de 0 a 100
Nivel 0–15: Fundamentos absolutos
Que aprender:
- Variables (
let/const), tipos primitivos,console.log() - Operadores:
+,-,*,/,===,!==,&&,||,! - Condicionales:
if/else, ternario - Bucles:
for,for...of,while - Arrays:
push(),pop(),length, acceso por indice,includes() - Funciones:
functiondeclaration, arrow functions
Proyecto: Adivina el numero — numero aleatorio, pistas mayor/menor, contador de intentos.
Nivel 15–30: Funciones, objetos y DOM
Que aprender:
- Objetos literales, propiedades dinámicas, shorthand
this,call,apply,bind- Spread, destructuring
map(),filter(),reduce(),find(),sort()- Closures
- TypedArrays, Symbol, BigInt
Proyecto: Gestor de tareas CLI — añadir, listar, completar, eliminar con persistencia JSON.
Nivel 30–45: POO, prototipos y asincronia básica
Que aprender:
- Clases ES6+,
extends,super(), campos privados# - Promesas,
Promise.all, async/await básico - File system:
fs.promises,path - Canvas 2D básico
Proyecto: Sistema de inventario — clases Producto, Inventario, persistencia asíncrona.
Nivel 45–60: Testing, módulos y herramientas
Que aprender:
- ESM:
import/export node:testovitest- ESLint + Prettier
- TypeScript básico
- Streams, Buffer,
fs.watch - Sharp para imágenes
Proyecto: Calculadora empaquetada — paquete npm ESM, tests, CI.
Nivel 60–75: APIs y async avanzado
Que aprender:
- Express/Fastify, middleware, rutas
- Prisma/Drizzle, migrations, CRUD
- Zod para validación
- JWT para autenticación
- Testing de APIs con Supertest
Proyecto: API de recetas — CRUD, autenticación JWT, validación Zod, Prisma SQLite.
Nivel 75–90: Producción y tiempo real
Que aprender:
- Docker, docker-compose (app + db + redis)
- WebSockets (Socket.IO), Redis, BullMQ
- Logging con pino/winston
- Rate limiting, CORS, Helmet
Proyecto: Dashboard de monitorización — tiempo real con WebSocket, Redis, Docker Compose.
Nivel 90–100: Arquitectura y microservicios
Que aprender:
- Microservicios, API Gateway
- Kafka/RabbitMQ, gRPC, GraphQL
- OpenTelemetry, Prometheus, Grafana
- Patrones: Circuit Breaker, Retry, Saga
- TypeScript avanzado
Proyecto: Sistema de notificaciones distribuido — microservicios, Kafka, Docker, OpenTelemetry.
🔗 Para saber más
Primeros pasos y configuración del entorno
Si prefieres ver directamente el “Hola Mundo” en JavaScript, consulta El Atlas del Hola Mundo.
El REPL de Node.js
node
> console.log("Hola mundo")
Hola mundo
> 2 + 2
4
> [1, 2, 3].map(x => x ** 2)
[ 1, 4, 9 ]
> .exit
Hola mundo (módulo ESM)
// hola.mjs — extensión .mjs para ES Modules explícito
function main() {
console.log("Hola mundo");
}
main();
Ejecuta: node hola.mjs
O en package.json añade "type": "module" y usa .js:
{
"type": "module",
"name": "mi-proyecto",
"version": "1.0.0"
}
Crear un proyecto npm
mkdir mi-proyecto && cd mi-proyecto
npm init -y # package.json básico
npm install express # instalar dependencia
npm install -D typescript # dev dependency
npx tsc --init # inicializar tsconfig.json
Editores recomendados
Los IDEs y editores más usados son VSCode, JetBrains y Neovim. Extensiones y configuraciones recomendadas.
VSCode y herramientas de calidad
Extensiones esenciales:
- ESLint (Microsoft) — linting de JS/TS
- Prettier (Prettier) — formateador automático
- JavaScript and TypeScript Nightly (Microsoft) — últimas features del language server
- npm Intellisense (Christian Kohler) — autocompletado en imports
Configuración mínima eslint.config.js (flat config, ESLint 9+):
import js from "@eslint/js";
export default [
js.configs.recommended,
{
rules: {
"no-unused-vars": "warn",
"no-console": "off",
"eqeqeq": ["error", "always"],
"curly": "error",
"semi": ["error", "always"],
},
},
];
Configuración .prettierrc:
{
"semi": true,
"singleQuote": false,
"tabWidth": 2,
"trailingComma": "all",
"printWidth": 100
}
TypeScript (tsconfig.json mínimo)
{
"compilerOptions": {
"target": "ES2022",
"module": "NodeNext",
"moduleResolution": "NodeNext",
"strict": true,
"esModuleInterop": true,
"skipLibCheck": true,
"outDir": "dist",
"rootDir": "src",
"declaration": true,
"sourceMap": true
},
"include": ["src"],
"exclude": ["node_modules", "dist"]
}
Ejecuta con npx tsc --watch o tsx (ejecución directa de TS): npx tsx src/index.ts.
Fuente: Node.js — Modules: Packages, ESLint docs, Prettier docs, TypeScript tsconfig
Paradigmas de programación
JavaScript es un lenguaje multiparadigma: puedes escribir código imperativo, orientado a objetos, funcional, event-driven y reactivo, a menudo combinándolos en un mismo programa. Su identidad real es la de un lenguaje event-driven con fuerte influencia funcional.
Programación imperativa
Describir paso a paso cómo se hace algo:
const numeros = [3, 1, 4, 1, 5, 9];
let suma = 0;
for (let i = 0; i < numeros.length; i++) {
suma += numeros[i];
}
console.log(suma); // 23
Programación orientada a objetos (prototipos y clases)
JS implementa POO mediante prototipos (no clases clásicas al estilo Java). Las clases ES6+ son azúcar sintáctico sobre prototipos:
// Prototipos
function Animal(nombre) {
this.nombre = nombre;
}
Animal.prototype.saludar = function () {
return `Hola, soy ${this.nombre}`;
};
const perro = new Animal("Rex");
console.log(perro.saludar()); // "Hola, soy Rex"
// Clases (mismo mecanismo interno)
class Gato extends Animal {
constructor(nombre) {
super(nombre);
}
maullar() {
return "Miau";
}
}
const gato = new Gato("Bigotes");
console.log(gato.saludar()); // "Hola, soy Bigotes"
console.log(typeof Gato); // "function"
console.log(Gato.prototype === Object.getPrototypeOf(gato)); // true
Programación funcional (callbacks, closures, map/filter/reduce)
JS trata las funciones como ciudadanos de primera clase: se asignan, pasan como argumento y se devuelven desde otras funciones.
Callbacks:
function procesar(datos, callback) {
return datos.map(callback);
}
console.log(procesar([1, 2, 3], (x) => x * 2)); // [2, 4, 6]
setTimeout(() => console.log("Temporizador"), 1000);
Closures (función que recuerda su ámbito de creación):
function crearMultiplicador(factor) {
return (numero) => numero * factor;
}
const duplicar = crearMultiplicador(2);
const triplicar = crearMultiplicador(3);
console.log(duplicar(5)); // 10
console.log(triplicar(5)); // 15
// Uso real: función con estado encapsulado
function crearContador(inicial = 0) {
let cuenta = inicial;
return {
incrementar: () => ++cuenta,
decrementar: () => --cuenta,
obtener: () => cuenta,
};
}
const c = crearContador(10);
console.log(c.incrementar()); // 11
console.log(c.obtener()); // 11
map, filter, reduce — las tres operaciones fundamentales:
const nums = [1, 2, 3, 4, 5, 6];
const pares = nums.filter((n) => n % 2 === 0); // [2, 4, 6]
const dobles = nums.map((n) => n * 2); // [2, 4, 6, 8, 10, 12]
const total = nums.reduce((acc, n) => acc + n, 0); // 21
// Composición funcional (pipeline):
const resultado = nums
.filter((n) => n % 2 === 0) // [2, 4, 6]
.map((n) => n * 2) // [4, 8, 12]
.reduce((acc, n) => acc + n, 0); // 24
Funciones puras e inmutabilidad:
// Impura (modifica el original)
const agregarImpuro = (arr, item) => {
arr.push(item);
return arr;
};
// Pura (devuelve nuevo array)
const agregarPuro = (arr, item) => [...arr, item];
const original = [1, 2];
const resultado = agregarPuro(original, 3);
console.log(original); // [1, 2] — sin modificar
console.log(resultado); // [1, 2, 3]
Programación event-driven
El núcleo de JavaScript en el navegador y Node.js está basado en eventos gestionados por el event loop:
// Navegador
document.querySelector("button")
.addEventListener("click", (event) => {
console.log("Clickeado", event);
});
// Node.js (EventEmitter nativo)
import { EventEmitter } from "events";
const emisor = new EventEmitter();
emisor.on("datos", (payload) => console.log("Recibido:", payload));
emisor.emit("datos", { id: 1, mensaje: "hola" });
Programación reactiva (RxJS)
Extiende el patrón observer con operadores para componer flujos de datos asíncronos:
import { fromEvent, map, filter, debounceTime } from "rxjs";
const input = document.querySelector("#buscador");
fromEvent(input, "input").pipe(
debounceTime(300),
map((e) => e.target.value),
filter((texto) => texto.length >= 3),
).subscribe((query) => {
console.log("Buscar:", query);
});
Tabla comparativa de paradigmas
| Paradigma | Unidad básica | Estado | Efectos secundarios | Soporte JS |
|---|---|---|---|---|
| Imperativo | Instrucciones | Mutable | Sí | Nativo |
| POO (prototipos) | Objetos | Propiedades mutables | Sí | Nativo |
| POO (clases) | Clases/instancias | Propiedades mutables | Sí | ES6+ |
| Funcional | Funciones | Inmutable (preferido) | Evitados | Nativo |
| Event-driven | Eventos | Callbacks/Listeners | Sí | Nativo |
| Reactivo | Streams/Observables | Flujo de datos | Gestionado | RxJS |
JS destaca por combinar event-driven (el event loop) con funcional (closures, map/filter/reduce). No hay que elegir uno — se mezclan constantemente.
Fuente: MDN — Introduction, RxJS docs, Exploring JS
Tipos de datos y variables
typeof y tipado dinámico
¿Qué es?
JavaScript tiene tipado dinámico: una variable puede cambiar de tipo en cualquier momento. No declaras el tipo — el intérprete lo determina en tiempo de ejecución. Usa typeof para inspeccionar el tipo actual.
- Analogía: es como una caja que puede contener cualquier cosa, y cambia de contenido sin avisar.
- 🔗 MDN — typeof
Sintaxis básica
typeof 42; // "number"
typeof "hola"; // "string"
typeof true; // "boolean"
typeof undefined; // "undefined"
typeof null; // "object" — bug histórico (no se corrigió por retrocompatibilidad)
typeof Symbol(); // "symbol"
typeof 42n; // "bigint"
typeof {}; // "object"
typeof []; // "object"
typeof (() => {}); // "function"
typeof NaN; // "number" — NaN es de tipo number
🧪 Cómo probarlo
node -e "console.log(typeof 42, typeof 'hola', typeof true, typeof undefined, typeof null)"
// Test con node:test (Node 20+)
import assert from "node:assert/strict";
assert.strictEqual(typeof 42, "number");
assert.strictEqual(typeof "hola", "string");
assert.strictEqual(typeof null, "object");
💡 Memoria y rendimiento
typeofes una operación O(1) — el tipo se almacena en los bits del valor (V8 usa puntero con tag)- No hay penalización por usar
typeofen bucles estrechos - ⚠️
typeof null === "object"es un bug de JS que no se puede corregir por retrocompatibilidad
✅ Buenas prácticas
- Usa
typeof x === "undefined"para verificar si una variable existe (seguro incluso si no está declarada) - Para detectar arrays:
Array.isArray(x)—typeofdevuelve “object” - Para detectar null:
x === null - ❌ No uses
typeofpara detectar objetos:typeof nullda “object”
🏗️ Metodología
- Usa
typeofpara type guards en tiempo de ejecución (ej: aceptar string o número en una función) - Alternativa moderna: TypeScript aporta tipos estáticos y
typeofen tiempo de tipo - En APIs públicas, valida tipos al inicio para dar errores claros
🔗 Para saber más
Primitivos: number, bigint, string, boolean, symbol, null, undefined
¿Qué es?
JavaScript tiene 7 tipos primitivos: number, bigint, string, boolean, symbol, null, undefined. Los primitivos son inmutables y se almacenan por valor en el stack.
Sintaxis básica
// Number (IEEE 754 double-precision)
const entero = 42;
const decimal = 3.14;
const notacion = 1.5e6; // 1500000
const binario = 0b1010; // 10
const octal = 0o77; // 63
const hex = 0xFF; // 255
const separador = 1_000_000; // 1000000
// Problemas de precisión
0.1 + 0.2; // 0.30000000000000004
0.1 + 0.2 === 0.3; // false
// BigInt (para enteros > 2^53 - 1)
const grande = 9007199254740993n;
const enorme = BigInt("123456789012345678901234567890");
5n / 2n; // 2n (trunca)
// String (UTF-16)
const simple = "hola";
const doble = 'mundo';
const template = `Hola ${simple}`;
const emoji = "😀";
emoji.length; // 2 — ¡no 1! (ocupa 2 unidades UTF-16)
[...emoji].length; // 1 — spread itera por caracteres Unicode
// Boolean
const verdad = true;
const falso = false;
// Symbol (único)
const ID = Symbol("id");
Symbol("id") === Symbol("id"); // false
// null (ausencia intencional) vs undefined (no asignado)
let a;
console.log(a); // undefined
const b = null;
console.log(b); // null
// NaN — único valor que no es igual a sí mismo
NaN === NaN; // false
Number.isNaN(NaN); // true
🧪 Cómo probarlo
node -e "
console.log(typeof 42n); // bigint
console.log(0.1 + 0.2 === 0.3); // false
console.log(NaN === NaN); // false
console.log([...'😀'].length); // 1
"
import assert from "node:assert/strict";
assert.strictEqual(typeof 42n, "bigint");
assert.strictEqual(Number.isNaN(NaN), true);
assert.strictEqual([..."😀"].length, 1);
💡 Memoria y rendimiento
| Tipo | Tamaño | Stack/Heap | Mutabilidad |
|---|---|---|---|
| number | 8 bytes (double) | Stack | Inmutable |
| bigint | Variable | Heap | Inmutable |
| string | 2 bytes x char (UTF-16) | Heap (internado) | Inmutable |
| boolean | 4 bytes | Stack | Inmutable |
| symbol | 16-32 bytes | Heap | Inmutable |
| null | 0 (referencia nula) | Stack | — |
| undefined | 0 | Stack | — |
- Number sigue IEEE 754: enteros exactos hasta ±2^53 (≈9e15)
- Strings inmutables: cualquier operación crea un nuevo string
- ⚠️ Concatenar muchos strings en bucle es O(n²) — usar array
.join()o template literals
✅ Buenas prácticas
- Usa
Number.EPSILONpara comparar floats:Math.abs(0.1 + 0.2 - 0.3) < Number.EPSILON - Para moneda, usa céntimos (integer) o librería como
decimal.js - Detecta NaN con
Number.isNaN()no conisNaN()(que coerce) - ❌ No mezcles BigInt con Number:
1n + 1lanza TypeError - ❌ No confíes en
==para null checks: usax === null || x === undefinedox == null
🏗️ Metodología
- Usa
constpor defecto,letcuando necesites reasignar, nuncavaren código moderno - Para cálculos financieros: usa BigInt con céntimos o
decimal.js - Para datos binarios: TypedArrays (más eficientes que manipular strings)
🔗 Para saber más
Object y Array
¿Qué es?
Los objetos son colecciones dinámicas de pares clave-valor (claves son strings o Symbols). Los arrays son objetos especializados con índices numéricos y propiedad length. No son arrays contiguos en memoria como en C/Rust — son diccionarios con índice como string.
Sintaxis básica
// Objetos
const obj = {
nombre: "Ana",
edad: 30,
saludar() { return `Hola, soy ${this.nombre}`; },
};
// Propiedades dinámicas
const clave = "email";
obj[clave] = "ana@email.com";
// Shorthand
const x = 1, y = 2;
const punto = { x, y };
// Métodos estáticos
Object.keys(obj);
Object.values(obj);
Object.entries(obj);
Object.fromEntries([["a", 1]]);
Object.freeze(obj); // shallow
Object.seal(obj); // impide añadir/borrar, permite modificar
// Descriptores
Object.defineProperty(obj, "fijo", {
value: 42,
writable: false,
enumerable: true,
configurable: false,
});
// Arrays
const arr = [10, 20, 30];
arr.push(2); // añade al final
arr.pop(); // quita del final
arr.unshift(0); // añade al inicio
arr.shift(); // quita del inicio
arr.splice(2, 1); // elimina 1 en indice 2
arr.slice(1, 3); // copia [1, 3)
// NO uses delete en arrays (deja hueco)
delete arr[1];
// Vaciar correctamente:
arr.length = 0; // mas eficiente que arr = []
// Arrays sparse (con huecos)
const sparse = [1, , , 4];
console.log(sparse.length); // 4
console.log(sparse[1]); // undefined
🧪 Cómo probarlo
node -e "
const obj = { a: 1, b: 2 };
console.log(Object.keys(obj)); // ['a', 'b']
console.log(Object.values(obj)); // [1, 2]
console.log(Object.entries(obj)); // [['a', 1], ['b', 2]]
"
import assert from "node:assert/strict";
const arr = [1, 2, 3];
assert.strictEqual(arr.length, 3);
arr.push(4);
assert.strictEqual(arr.length, 4);
assert.deepStrictEqual(Object.keys({ a: 1, b: 2 }), ["a", "b"]);
💡 Memoria y rendimiento
| Operación | Array | Object |
|---|---|---|
| Acceso por clave/indice | O(1) | O(1) |
| Inserción/borrado final | O(1) amortizado | N/A |
| Inserción/borrado inicio | O(n) reindexa | N/A |
| Iteración | O(n) | O(n) |
| Busqueda de valor | O(n) .includes() | O(n) Object.values() |
- V8 optimiza arrays como arrays reales (elementos contiguos) si son densos y homogéneos
- Al añadir tipos mixtos o huecos, V8 los trata como diccionarios lentos
- ⚠️
deleteen arrays deja huecos y desoptimiza Object.freeze()es shallow — las propiedades anidadas siguen siendo mutables
✅ Buenas prácticas
- Usa
Mapen lugar de Object para colecciones dinámicas (claves arbitrarias, mejor rendimiento) - Prefiere métodos inmutables:
.toSorted(),.toReversed(),.toSpliced()(ES2023) - ❌ No modifiques el array mientras iteras con
forEach/map - ❌ No uses
for...inen arrays (itera también propiedades heredadas) - Para copia superficial:
[...arr]o{...obj}. Para profunda:structuredClone(obj)
🏗️ Metodología
- Objetos: ideales para registros con campos fijos conocidos
- Arrays: para listas ordenadas del mismo tipo
- Usa
Mappara diccionarios que se modifican frecuentemente (mejor rendimiento que Object) - En APIs, prefiere objetos planos sobre arrays cuando devuelves datos anidados
🔗 Para saber más
Colecciones: Map, Set, WeakMap, WeakSet
¿Qué es?
Colecciones introducidas en ES6 para cubrir carencias de Object y Array. Map permite claves de cualquier tipo y preserva orden de inserción. Set almacena valores únicos. WeakMap/WeakSet usan referencias débiles que no impiden el garbage collection.
Sintaxis básica
// Map — clave-valor con cualquier tipo de clave
const mapa = new Map();
const objKey = { id: 1 };
mapa.set(objKey, "valor asociado");
mapa.set("string", "otro");
mapa.set(NaN, "NaN como clave");
mapa.get(objKey); // "valor asociado"
mapa.has("string"); // true
mapa.size; // 3
for (const [k, v] of mapa) console.log(k, v);
// Set — valores unicos
const conjunto = new Set([1, 2, 3, 2, 1, 3, 4]);
[...conjunto]; // [1, 2, 3, 4]
conjunto.add(5);
conjunto.has(3); // true (O(1))
conjunto.delete(1);
// Operaciones entre sets
const a = new Set([1, 2, 3]), b = new Set([2, 3, 4]);
const union = new Set([...a, ...b]); // {1, 2, 3, 4}
const interseccion = new Set([...a].filter(x => b.has(x))); // {2, 3}
const diferencia = new Set([...a].filter(x => !b.has(x))); // {1}
// WeakMap — claves objeto, referencias debiles
const cache = new WeakMap();
let comp = { id: 1 };
cache.set(comp, { creado: Date.now() });
comp = null; // WeakMap libera la entrada automaticamente
// WeakSet — valores objeto unicos con referencia debil
const visitados = new WeakSet();
let nodo = { id: 1 };
visitados.add(nodo);
nodo = null; // desaparece del WeakSet
🧪 Cómo probarlo
node -e "
const m = new Map(); m.set('a', 1); m.set('b', 2);
console.log(m.size); // 2
console.log(m.get('a')); // 1
console.log(m.has('c')); // false
const s = new Set([1,2,2,3]);
console.log([...s]); // [1, 2, 3]
"
import assert from "node:assert/strict";
const m = new Map([["a", 1], ["b", 2]]);
assert.strictEqual(m.size, 2);
assert.strictEqual(m.get("a"), 1);
const s = new Set([1, 2, 2, 3]);
assert.strictEqual(s.size, 3);
assert.ok(s.has(2));
💡 Memoria y rendimiento
| Colección | Búsqueda | Inserción | Borrado | Iteración |
|---|---|---|---|---|
| Map | O(1) | O(1) | O(1) | O(n) |
| Set | O(1) | O(1) | O(1) | O(n) |
| WeakMap | O(1) | O(1) | O(1) | No iterable |
| WeakSet | O(1) | O(1) | O(1) | No iterable |
| Object (como diccionario) | O(1) | O(1) | O(1) | O(n) |
- WeakMap/WeakSet no tienen
.size,.keys(),.values(), ni son iterables — las referencias débiles pueden desaparecer en cualquier momento - Map preserva orden de inserción, Object también (ES2015+), pero Object tiene herencia de prototipo
- ⚠️ WeakMap evita memory leaks: cuando el objeto clave se recolecta, la entrada desaparece automaticamente
✅ Buenas prácticas
- Usa
Mappara diccionarios que se modifican frecuentemente (mejor que Object plano) - Usa
Setpara deduplicación rápida O(n) en lugar de filter/indexOf O(n²) - Usa
WeakMappara metadatos asociados a objetos DOM o instancias (no impide GC) - ❌ No asumas que Object.keys() incluye Symbols (usa
Object.getOwnPropertySymbols) - ❌ No uses objetos como claves de Map si esperas encontrar la clave por igualdad estructural (compara por referencia)
🏗️ Metodología
- Colección dinámica con claves arbitrarias → Map
- Valores únicos con búsqueda rápida → Set
- Metadatos asociados a objetos (caches) → WeakMap
- Tracking de objetos visitados → WeakSet
- Datos planos con claves string conocidas → Object
🔗 Para saber más
TypedArrays y Buffer
¿Qué es?
TypedArrays son vistas de ArrayBuffer que permiten leer/escribir memoria contigua con un tipo fijo. Ideales para datos binarios, WebGL, audio, imágenes. En Node.js, Buffer extiende Uint8Array para I/O de archivos y red.
Sintaxis básica
// ArrayBuffer subyacente
const buffer = new ArrayBuffer(16); // 16 bytes
const vista32 = new Int32Array(buffer);
const vista8 = new Uint8Array(buffer);
vista32[0] = 0x12345678;
// Int32Array
const int32 = new Int32Array(4);
int32[0] = 42;
int32[1] = -7;
// Uint8Array
const bytes = new Uint8Array([72, 101, 108, 108, 111]); // "Hello"
new TextDecoder().decode(bytes); // "Hello"
// Float64Array
const floats = new Float64Array(3);
floats[0] = 3.141592653589793;
// Uint8ClampedArray (satura)
const pixeles = new Uint8ClampedArray(4);
pixeles[0] = 300; pixeles[0]; // 255 (saturado)
pixeles[1] = -10; pixeles[1]; // 0 (saturado)
// Tipos disponibles:
// Int8Array, Uint8Array, Uint8ClampedArray, Int16Array, Uint16Array,
// Int32Array, Uint32Array, Float32Array, Float64Array,
// BigInt64Array, BigUint64Array
// Buffer (Node.js)
import { Buffer } from "buffer";
const buf1 = Buffer.alloc(10); // 10 bytes a 0
const buf2 = Buffer.from("Hola");
const combined = Buffer.concat([buf2, Buffer.from(" Mundo")]);
🧪 Cómo probarlo
node -e "
const buf = Buffer.from('Hola');
console.log(buf.toString('hex')); // 486f6c61
console.log(new TextDecoder().decode(new Uint8Array([72,105]))); // Hi
"
import assert from "node:assert/strict";
const buf = Buffer.from("Hola");
assert.strictEqual(buf.toString(), "Hola");
assert.strictEqual(buf[0], 0x48);
💡 Memoria y rendimiento
- ArrayBuffer es memoria contigua (a diferencia de Array que es un objeto esparcido)
- TypedArrays son significativamente más rápidos que arrays normales para datos numéricos
Buffer.allocUnsafe()asigna memoria sin inicializar (rápido, pero puede exponer datos sensibles)Buffer.alloc()es más seguro (inicializa a 0) pero más lento- ⚠️
.slice()en Buffer comparte memoria — modificar el slice afecta al original. Usa.subarray()para copia segura
✅ Buenas prácticas
- Siempre llenar
Buffer.allocUnsafe()con.fill(0)antes de usar (seguridad) - Para strings y datos textuales, no necesitas Buffer — usa strings directamente
- Usa
TextEncoder/TextDecoderpara conversión string a bytes en lugar de manipulación manual - ❌ No asumas endianness — usa
DataViewpara lecturas multi-byte portables
🏗️ Metodología
- Canvas/WebGL →
Uint8ClampedArray(pixeles RGBA) - Network I/O →
Buffer(Node.js) - Audio →
Float32Array(muestras de audio) - Archivos binarios →
Uint8Array+DataView
🔗 Para saber más
Mutabilidad: const no es inmutabilidad, hoisting, TDZ y scope
¿Qué es?
const evita reasignación de la referencia, no congela el contenido del objeto. El hoisting eleva declaraciones al inicio del ámbito. let/const tienen Temporal Dead Zone (TDZ): existen en el ámbito pero son inaccesibles hasta la declaración.
Sintaxis básica
// const no es inmutabilidad
const PI = 3.1416;
// PI = 3; // TypeError
const usuario = { nombre: "Ana" };
usuario.nombre = "Luis"; // ✅ mutación permitida
usuario.email = "l@e.com";// ✅ nuevo campo
// usuario = {}; // TypeError
// Object.freeze (superficial)
const congelado = Object.freeze({ nombre: "Ana", dir: { ciudad: "Madrid" } });
congelado.dir.ciudad = "Barcelona"; // ✅ freeze es shallow
// Hoisting y TDZ
console.log(a); // undefined (var hoisting)
var a = 5;
// console.log(b); // ReferenceError (TDZ)
let b = 10;
// Function declarations: hoisting completo
saludar(); // "Hola"
function saludar() { console.log("Hola"); }
// Function expressions: según la variable
var foo = function () {};
// Scope: global, function y block
const globalVar = "accesible desde cualquier lugar";
function ejemplo() {
var functionVar = "solo function scope";
if (true) {
let blockLet = "solo block scope";
const blockConst = "tambien block scope";
var blockVar = "NO tiene block scope";
}
}
🧪 Cómo probarlo
node -e "
const o = { a: 1 };
o.a = 2; console.log(o.a); // 2
console.log(typeof undeclaredVar); // undefined (seguro)
"
import assert from "node:assert/strict";
const obj = { a: 1 };
obj.a = 2;
assert.strictEqual(obj.a, 2);
assert.throws(() => { const x = 1; x = 2; }, TypeError);
💡 Memoria y rendimiento
let/consttienen mejor rendimiento quevaren bucles (ámbito más reducido)- Object.freeze() tiene coste en rendimiento (marca propiedades como no-writable)
- El hoisting es un comportamiento del parser, no tiene coste en runtime
- ⚠️ Las closures en bucles con
varcausan el problema clásico (todas comparten la misma variable)
✅ Buenas prácticas
- Usa
constpor defecto,letsolo cuando necesites reasignar - Nunca uses
varen código moderno - Si necesitas inmutabilidad real, usa
Object.freeze()profundo (libreríaimmerostructuredClone) - ❌ No confíes en
constpara inmutabilidad — es solo no-reasignación - ❌ No asumas que hoisting hace accesible la variable antes de su declaración (TDZ)
🏗️ Metodología
constpara valores que no cambian de referencia (constantes, imports, config)letpara contadores de bucle, acumuladores, flagsvarsolo cuando necesitas compatibilidad con navegadores muy antiguos (IE11)
🔗 Para saber más
Garbage Collection
¿Qué es?
V8 (Chrome/Node.js) usa el recolector Orinoco: GC generacional con mark-sweep compactado, concurrente e incremental. Divide objetos en young generation (nursery) y old generation. Los objetos inalcanzables desde la raíz se recolectan automáticamente.
Sintaxis básica
let vivo = { data: "importante" }; // alcanzable → vive
let muerto = { data: "basura" }; // alcanzable
muerto = null; // inalcanzable → GC lo limpia
// Ciclos de referencia: V8 los detecta
function test() {
let a = {}, b = {};
a.ref = b; b.ref = a;
// Al salir, a y b son inalcanzables → se recolectan
}
// Forzar GC (solo con --expose-gc):
globalThis.gc?.();
🧪 Cómo probarlo
node --expose-gc -e "
let x = { data: new Array(1000000).fill('a') };
globalThis.gc(); // aún referenciado
x = null;
globalThis.gc(); // ahora se recolecta
console.log('recolectado');
"
💡 Memoria y rendimiento
- Young generation: objetos nuevos. GC menor (Scavenger) — rápido, pausas < 1ms
- Old generation: objetos que sobreviven varios GC. GC mayor (Mark-Sweep-Compact) — más lento
- Pausas típicas: < 1ms (menor), < 10ms (mayor) en V8 moderno
- ⚠️ Memory leaks comunes:
- Variables globales accidentales
- Closures que retienen referencias grandes
- Timers sin limpiar (
setIntervalsinclearInterval) - Listeners de eventos nunca eliminados
- Caché sin límite (crece indefinidamente)
- DOM detached (referencia a elemento eliminado)
✅ Buenas prácticas
- Usa
WeakMap/WeakSetpara metadatos asociados a objetos - Limpia timers:
const id = setInterval(fn, 1000); clearInterval(id); - Elimina listeners:
emitter.off("evento", callback)o usaronce() - En closures grandes, asigna
nulla referencias pesadas tras usarlas - ❌ No asumas que
delete obj.proplibera memoria (solo elimina la propiedad)
🏗️ Metodología
- Para apps normales, confía en el GC — no optimices prematuramente
- Para caches, usa LRU (librería
lru-cache) o WeakMap - Para streams y archivos grandes, libera buffers explícitamente
- Monitorea memoria con
process.memoryUsage()en Node.js
🔗 Para saber más
Control de flujo y modularidad
Condicionales: if/else, ternario, switch, short-circuit
¿Qué es?
Las estructuras condicionales permiten ejecutar diferentes bloques de código según condiciones booleanas. JavaScript tiene if/else, el operador ternario (cond ? a : b), switch, y short-circuit con &&, ||, ??.
Sintaxis básica
// if/else
const edad = 18;
if (edad < 12) {
console.log("Niño");
} else if (edad < 18) {
console.log("Adolescente");
} else if (edad < 65) {
console.log("Adulto");
} else {
console.log("Jubilado");
}
// Ternario
const mensaje = edad >= 18 ? "Mayor de edad" : "Menor de edad";
// switch
const dia = new Date().getDay();
switch (dia) {
case 0: console.log("Domingo"); break;
case 6: console.log("Sábado"); break;
case 1: case 2: case 3: case 4: case 5: console.log("Laborable"); break;
default: console.log("Dia invalido");
}
// Short-circuit
0 && "hola"; // 0
1 || "default"; // 1
null ?? "default"; // "default" (?? solo null/undefined)
🧪 Cómo probarlo
node -e "
const edad = 20;
const msg = edad >= 18 ? 'Adulto' : 'Menor';
console.log(msg); // Adulto
console.log(0 && 'x'); // 0
console.log(null ?? 'predet'); // predet
"
import assert from "node:assert/strict";
assert.strictEqual(18 >= 18 ? "Adulto" : "Menor", "Adulto");
assert.strictEqual(0 && "x", 0);
assert.strictEqual(null ?? "predet", "predet");
💡 Memoria y rendimiento
- Ternario y short-circuit no tienen penalización frente a if/else (compilan a lo mismo)
switches O(1) con valores enteros (tabla de saltos), O(n) con strings||y&&cortocircuitan: no evalúan el segundo operando si el primero determina el resultado
✅ Buenas prácticas
- Prefiere ternario para asignaciones simples, if/else para lógica compleja (~3+ ramas)
- Evita ternarios anidados: ilegibles. Usa if/else o switch
- Usa
??para valores por defecto (solo null/undefined), no||(que reacciona a cualquier falsy) - ❌ No abuses de short-circuit para control de flujo — puede ser críptico
🏗️ Metodología
- Condiciones binarias simples → ternario
- Múltiples condiciones mutuamente excluyentes → switch o if/else if
- Asignación condicional →
??o|| - Para APIs, siempre valida entrada con condicionales al inicio
🔗 Para saber más
Bucles: for, while, do-while, for-in, for-of
¿Qué es?
JavaScript ofrece múltiples estructuras de bucle. for clásico para contadores, while para condiciones, for...of para iterables, for...in para keys de objetos. Desde ES6, for...of es la opción preferida para arrays.
Sintaxis básica
// for clasico
for (let i = 0; i < 5; i++) console.log(i); // 0 1 2 3 4
// while
let i = 0;
while (i < 5) console.log(i++);
// do-while (se ejecuta al menos una vez)
let j = 0;
do { console.log(j); } while (j > 0); // imprime 0
// for-in: itera sobre KEYS (incluye herencia)
const arr = ["a", "b", "c"];
const obj = { x: 1, y: 2 };
for (const k in arr) console.log(k); // "0", "1", "2"
for (const k in obj) console.log(k); // "x", "y"
// for-of: itera sobre VALUES (requiere iterable)
for (const v of arr) console.log(v); // "a", "b", "c"
for (const c of "Hola") console.log(c); // H, o, l, a
for (const [k, v] of new Map([["a", 1]])) console.log(k, v);
🧪 Cómo probarlo
node -e "
const arr = ['a', 'b', 'c'];
let r = '';
for (const v of arr) r += v;
console.log(r); // abc
"
import assert from "node:assert/strict";
const arr = ["a", "b", "c"];
let r = "";
for (const v of arr) r += v;
assert.strictEqual(r, "abc");
💡 Memoria y rendimiento
forclásico es el más rápido (menos overhead de iteración)for...ofen arrays tiene overhead mínimo (usa el protocolo iterable)for...ines lento (recorre cadena de prototipos)- ⚠️
for...inen arrays es antipatrón: incluye propiedades heredadas y no heredadas
✅ Buenas prácticas
- Usa
for...ofpara arrays por defecto (legible, seguro) - Usa
forclásico cuando necesites el índice o control fino - Nunca uses
for...inen arrays - Prefiere
map/filter/reducesobre bucles cuando transformas colecciones
🏗️ Metodología
- Iterar array →
for...of(o.forEach()si no necesitas break) - Transformar array →
.map(),.filter(),.reduce() - Bucle con contador →
for (let i = 0; i < n; i++) - Iterar keys de objeto →
for...in(conhasOwnPropertycheck)
🔗 Para saber más
Métodos de iteración: forEach, map, filter, reduce
¿Qué es?
JavaScript proporciona métodos funcionales para trabajar con arrays que evitan bucles imperativos. map transforma cada elemento, filter selecciona elementos, reduce acumula, find busca, some/every verifican condiciones.
Sintaxis básica
const nums = [1, 2, 3, 4, 5];
// forEach: ejecuta por cada elemento (sin return)
nums.forEach((n, i) => console.log(`[${i}]: ${n}`));
// map: transforma cada elemento, nuevo array
const dobles = nums.map((n) => n * 2); // [2, 4, 6, 8, 10]
// filter: selecciona los que cumplen
const pares = nums.filter((n) => n % 2 === 0); // [2, 4]
// reduce: acumula
const suma = nums.reduce((acc, n) => acc + n, 0); // 15
// Caso avanzado: agrupar
const agrupado = nums.reduce((acc, n) => {
const key = n % 2 === 0 ? "pares" : "impares";
(acc[key] ??= []).push(n);
return acc;
}, {});
// find: primer elemento que cumple
nums.find((n) => n > 3); // 4
// some: algun elemento cumple?
nums.some((n) => n > 10); // false
// every: todos cumplen?
nums.every((n) => n > 0); // true
🧪 Cómo probarlo
node -e "
const n = [1, 2, 3, 4, 5];
console.log(n.map(x => x * 2)); // [2,4,6,8,10]
console.log(n.filter(x => x%2)); // [1,3,5]
console.log(n.reduce((a,b) => a+b, 0)); // 15
"
import assert from "node:assert/strict";
assert.deepStrictEqual([1, 2, 3].map(x => x * 2), [2, 4, 6]);
assert.deepStrictEqual([1, 2, 3].filter(x => x > 1), [2, 3]);
assert.strictEqual([1, 2, 3].reduce((a, b) => a + b, 0), 6);
💡 Memoria y rendimiento
mapcrea un nuevo array (usa 2x memoria del original)filtercrea un nuevo array- Encadenar map/filter crea arrays intermedios — para datasets grandes (>100k), considera un solo
reduceo un buclefor forEachno tiene return y no se puede encadenar- ⚠️
reducesin valor inicial usa el primer elemento como acumulador (peligro con arrays vacíos)
✅ Buenas prácticas
- Prefiere
map/filter/reducesobre bucles imperativos (declarativo, sin efectos secundarios) - Usa
forEachsolo para efectos secundarios (logging, I/O) - Para rendimiento crítico, un bucle
fores 2-5x más rápido quereduce - ❌ No modifiques el array original dentro de
map/filter— deben ser funciones puras
🏗️ Metodología
- Transformación 1:1 →
map - Selección →
filter - Acumulación →
reduce - Búsqueda →
find(primero) ofilter(todos) - Verificación →
some(algún) oevery(todos) - Para pipelines complejos, considera
flatMap,groupBy,toSorted
🔗 Para saber más
Excepciones: try/catch/finally y throw
¿Qué es?
El manejo de errores en JavaScript usa try/catch/finally. Puedes lanzar errores con throw. Los errores son objetos con name, message y stack. Puedes crear errores personalizados extendiendo Error.
Sintaxis básica
// Básico
try {
JSON.parse("invalido");
} catch (error) {
console.error("Error:", error.message);
} finally {
console.log("Siempre se ejecuta");
}
// Errores personalizados
class ValidationError extends Error {
constructor(campo, mensaje) {
super(mensaje);
this.name = "ValidationError";
this.campo = campo;
}
}
class NotFoundError extends Error {
constructor(recurso, id) {
super(`${recurso} con id ${id} no encontrado`);
this.name = "NotFoundError";
this.statusCode = 404;
}
}
function buscarUsuario(id) {
if (id <= 0) throw new ValidationError("id", "Debe ser positivo");
throw new NotFoundError("Usuario", id);
}
// Manejo por tipo
try {
buscarUsuario(-1);
} catch (err) {
if (err instanceof ValidationError) {
console.error("Validacion:", err.campo, err.message);
} else if (err instanceof NotFoundError) {
console.error("No encontrado:", err.message, `(HTTP ${err.statusCode})`);
} else {
console.error("Inesperado:", err);
}
}
🧪 Cómo probarlo
node -e "
try { JSON.parse('invalido'); } catch (e) {
console.log(e.name, e.message);
}
"
import assert from "node:assert/strict";
assert.throws(() => JSON.parse("invalido"), SyntaxError);
assert.doesNotThrow(() => JSON.parse('"ok"'));
💡 Memoria y rendimiento
- Lanzar errores es caro (construye stack trace). No uses
throwpara control de flujo normal try/catchsin error tiene overhead despreciable (~1%)- El stack trace se construye en el momento del
throw, no delcatch - ⚠️ Errores dentro de callbacks/promesas no capturados por
try/catchexterno
✅ Buenas prácticas
- Siempre lanza objetos
Error(o subclases), no strings o números - Usa
instanceofpara distinguir tipos de error - En APIs asíncronas, captura errores con
try/catchalrededor deawait - ❌ No captures errores genéricos sin relanzar los que no esperas
- ❌ No uses
throwpara control de flujo
🏗️ Metodología
- Errores de validación →
ValidationError(400) - Errores de negocio → subclases de
ErrorconstatusCode - Errores inesperados → captura genérica, log, relanza
- En APIs Express/Fastify, usa middleware de error centralizado
🔗 Para saber más
Módulos: ESM vs CommonJS, import/export, dynamic import
¿Qué es?
JavaScript tiene dos sistemas de módulos: ESM (ES Modules, estándar moderno con import/export) y CommonJS (CJS, legacy de Node.js con require/module.exports). ESM es estático (análisis en parseo), CJS es dinámico (ejecución).
Sintaxis básica
// ESM
// 📁 math.js
export const PI = 3.1416;
export function sumar(a, b) { return a + b; }
export default class Calc { mul(a, b) { return a * b; } }
// 📁 main.js
import Calc, { PI, sumar } from "./math.js";
import * as Math from "./math.js"; // namespace
// CommonJS
// 📁 math.cjs
const PI = 3.1416;
function sumar(a, b) { return a + b; }
module.exports = { PI, sumar };
// 📁 main.cjs
const { PI, sumar } = require("./math.cjs");
// Dynamic import
const modulo = await import("./ruta/modulo.js");
🧪 Cómo probarlo
echo 'export const hola = "mundo"' > /tmp/test_mod.mjs
node -e "import('/tmp/test_mod.mjs').then(m => console.log(m.hola))"
import assert from "node:assert/strict";
// Test con import dinámico
const mod = await import("path");
assert.ok(mod.join);
Diferencias ESM vs CommonJS
| Característica | ESM | CommonJS |
|---|---|---|
| Sintaxis | import/export | require()/module.exports |
| Carga | Asincrona (parsea todo antes) | Sincrona (ejecuta mientras importa) |
| Ambito | Strict mode siempre | Sloppy mode por defecto |
this en modulo | undefined | module.exports |
| Analisis | Estático (parseo) | Dinámico (ejecución) |
| Top-level await | ✅ | ❌ |
| Tree shaking | ✅ automático | ❌ |
| Extension | .mjs o "type": "module" | .cjs o "type": "commonjs" |
| Import dinámico | import() (promesa) | require() (sincrono) |
💡 Memoria y rendimiento
- ESM permite tree shaking: bundlers eliminan exports no usados en producción
- CJS carga módulos sincronamente (bloqueante), ESM es asincrono (no bloqueante)
import()dinámico permite code splitting (carga bajo demanda)- ⚠️ CJS en Node.js cachea módulos en
require.cache
✅ Buenas prácticas
- Usa ESM para proyectos nuevos (estándar, tree shaking, top-level await)
- CommonJS solo para herramientas CLI o compatibilidad con módulos legacy
- Usa extensiones
.mjs/.cjspara control explícito del sistema de módulos - ❌ No mezcles
importyrequire()en el mismo archivo - ❌ No asumas que
require()es disponible en ESM (no lo es)
🏗️ Metodología
- Aplicaciones nuevas → ESM con
"type": "module"en package.json - Librerías para npm → ESM + CJS (dual package)
- Scripts simples →
.mjspara ESM explícito - Herramientas CLI → CJS (más compatible)
🔗 Para saber más
Sistema de archivos (Node.js)
Node.js tiene un potente módulo fs (file system) para trabajar con archivos, path para rutas, stream para flujos, buffer para datos binarios y zlib para compresión.
fs.readFile / fs.writeFile (sincrono y callback)
¿Qué es?
El módulo fs proporciona tres APIs: sincrona (bloqueante), callbacks (no bloqueante) y promesas (fs/promises). La API de promesas con async/await es la recomendada.
Sintaxis básica
import fs from "fs";
// Sincrono (bloqueante)
const data = fs.readFileSync("archivo.txt", "utf8");
fs.writeFileSync("salida.txt", "Contenido del archivo");
// Callback (no bloqueante)
fs.readFile("archivo.txt", "utf8", (err, data) => {
if (err) { console.error("Error:", err.message); return; }
console.log(data);
});
fs.writeFile("log.txt", "nueva linea\n", { flag: "a" }); // append
🧪 Cómo probarlo
node -e "
import('fs/promises').then(async fs => {
await fs.writeFile('/tmp/test.txt', 'Hola mundo');
const data = await fs.readFile('/tmp/test.txt', 'utf8');
console.log(data); // Hola mundo
});
"
import assert from "node:assert/strict";
import fs from "fs/promises";
const path = "/tmp/test_assert.txt";
await fs.writeFile(path, "test");
const data = await fs.readFile(path, "utf8");
assert.strictEqual(data, "test");
💡 Memoria y rendimiento
readFilecarga todo el archivo en memoria — peligro con archivos > 100MB- Para archivos grandes (> 100MB), usa streams (
createReadStream) - La API sincrona bloquea el event loop — nunca en servidores de producción
writeFilecon{ flag: "a" }abre, escribe y cierra cada vez — para logs frecuentes usa streams
✅ Buenas prácticas
- Usa
fs/promisesconasync/await(moderno, legible, manejo de errores) - Usa streams para archivos grandes,
readFilepara pequeños (< 50MB) - Siempre usa
try/catchcon la API de promesas - ❌ No uses
readFileSyncen servidores web (bloquea el hilo)
🏗️ Metodología
- Archivos de configuración (< 1MB) →
readFile(promesas) - Logs en tiempo real →
createWriteStream - Archivos grandes (> 100MB) → streams con
pipe - Procesamiento por lotes →
readFile+writeFileen workers
🔗 Para saber más
path module y rutas
¿Qué es?
path proporciona utilidades para trabajar con rutas de archivos: unir, resolver, extraer componentes, convertir rutas relativas a absolutas. Es multiplataforma (maneja separadores Windows vs POSIX).
Sintaxis básica
import path from "path";
path.basename("/usr/local/bin/node"); // "node"
path.dirname("/usr/local/bin/node"); // "/usr/local/bin"
path.extname("foto.jpg"); // ".jpg"
path.join("/usr", "local", "bin"); // "/usr/local/bin"
path.resolve("src", "..", "dist"); // ruta absoluta desde cwd
path.parse("/home/user/file.txt");
// { root: "/", dir: "/home/user", base: "file.txt", ext: ".txt", name: "file" }
// En ESM (no hay __dirname):
import { fileURLToPath } from "url";
const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);
🧪 Cómo probarlo
node -e "
import('path').then(p => {
console.log(p.basename('/a/b/c.js')); // c.js
console.log(p.extname('/a/b/c.js')); // .js
});
"
💡 Memoria y rendimiento
path.joinypath.resolveson operaciones O(n) sobre la longitud de la ruta- No hay penalización significativa — se usan una vez al inicio típicamente
- ⚠️
path.resolvesin argumentos devuelve el directorio actual (process.cwd())
✅ Buenas prácticas
- Usa siempre
path.joinopath.resolveen lugar de concatenar strings con/ - Nunca asumas el separador (
/en POSIX,\en Windows) —path.seplo maneja - Para rutas URL (web), usa
URLyURLSearchParams, nopath
🏗️ Metodología
- Rutas absolutas →
path.resolve - Rutas relativas →
path.join - Separación de componentes →
path.parse,path.basename,path.dirname - Rutas relativas entre dos directorios →
path.relative
🔗 Para saber más
Streams (pipe, transform)
¿Qué es?
Los streams permiten procesar datos por fragmentos (chunks) sin cargar todo en memoria. Hay 4 tipos: readable, writable, transform, duplex. Ideales para archivos grandes, network I/O, pipelines de transformación.
Sintaxis básica
import { createReadStream, createWriteStream } from "fs";
import { Transform, pipeline } from "stream";
// Transform: modifica datos en transito
const upperCase = new Transform({
transform(chunk, encoding, callback) {
this.push(chunk.toString().toUpperCase());
callback();
},
});
// Pipe: readable → transform → writable
createReadStream("input.txt")
.pipe(upperCase)
.pipe(createWriteStream("output.txt"))
.on("finish", () => console.log("Completado"));
// pipeline (con promesa, manejo de errores automático):
import { pipeline } from "stream/promises";
await pipeline(
createReadStream("input.txt"),
upperCase,
createWriteStream("output.txt"),
);
// Leer por chunks
const rs = createReadStream("archivo.txt", {
encoding: "utf8",
highWaterMark: 64 * 1024, // 64KB por chunk
});
rs.on("data", (chunk) => console.log("Chunk:", chunk.length, "bytes"));
🧪 Cómo probarlo
echo "Hola mundo" > /tmp/test_stream.txt
node -e "
import { createReadStream, createWriteStream } from 'fs';
import { pipeline } from 'stream/promises';
await pipeline(
createReadStream('/tmp/test_stream.txt'),
createWriteStream('/tmp/test_copia.txt')
);
console.log('OK');
"
import assert from "node:assert/strict";
import { Readable } from "stream";
const chunks = [];
const rs = new Readable({
read() { this.push("data"); this.push(null); }
});
rs.on("data", c => chunks.push(c));
await new Promise(r => rs.on("end", r));
assert.strictEqual(Buffer.concat(chunks).toString(), "data");
💡 Memoria y rendimiento
- Los streams usan memoria proporcional al chunk size (default 64KB), no al archivo completo
highWaterMarkcontrola el buffering interno (default 16KB para readable, 16KB para writable)- Backpressure: el stream readable frena automáticamente si el writable está saturado
- ⚠️ Si no consumes el stream (sin
datalistener opipe), se bufferiza en memoria
✅ Buenas prácticas
- Usa
pipelineen lugar de.pipe()(maneja backpressure y errores automáticamente) - Para transformaciones, usa
Transformen lugar de readable + writable separados - Siempre maneja eventos
erroren streams - ❌ No conviertas streams enteros a buffer con métodos manuales a menos que sea necesario
🏗️ Metodología
- Archivos grandes (> 100MB) → streams
- Compresión en vuelo →
zlib+ pipeline - Procesamiento ETL → Transform streams encadenados
- Respuestas HTTP → pipe del stream directamente a
res(Response)
🔗 Para saber más
Buffer y zlib
¿Qué es?
Buffer (Node.js) es una subclase de Uint8Array para trabajar con datos binarios. zlib proporciona compresión/descompresión gzip, brotli, deflate. Ambos son esenciales para I/O de red, archivos binarios y HTTP.
Sintaxis básica
import { Buffer } from "buffer";
// Crear buffers
const buf1 = Buffer.alloc(10); // 10 bytes, inicializados a 0
const buf2 = Buffer.alloc(10, 0xff); // 10 bytes, todos 0xff
const buf3 = Buffer.from("Hola");
const buf4 = Buffer.from([0x48, 0x6f, 0x6c, 0x61]);
const combined = Buffer.concat([buf3, Buffer.from(" Mundo")]);
// zlib
import { createGzip, createGunzip } from "zlib";
import { pipeline } from "stream/promises";
await pipeline(
createReadStream("datos.json"),
createGzip(),
createWriteStream("datos.json.gz"),
);
// Comprimir en memoria
import { gzip, gunzip } from "zlib";
const data = "texto repetido ".repeat(1000);
const compressed = await new Promise((resolve, reject) => {
gzip(data, (err, result) => err ? reject(err) : resolve(result));
});
✅ Buenas prácticas
- Usa
Buffer.allocUnsafe()solo cuando sepas que llenarás el buffer inmediatamente - Para compresión HTTP, usa
zlibcon pipeline para streaming - ❌ No asumas que
Bufferestá disponible en navegadores (usaTextEncoder/TextDecoder)
🔗 Para saber más
readdir y watch
¿Qué es?
fs.readdir lista el contenido de un directorio. fs.watch observa cambios en archivos o directorios. Útiles para herramientas de desarrollo, recarga en caliente, procesamiento por lotes.
Sintaxis básica
import fs from "fs/promises";
// Listar archivos
const archivos = await fs.readdir("./");
const entries = await fs.readdir("./", { withFileTypes: true });
for (const entry of entries) {
if (entry.isFile()) {
const stats = await fs.stat(entry.name);
console.log(`${entry.name} (${stats.size} bytes)`);
}
}
// Observar cambios
import fs from "fs";
const watcher = fs.watch("./src", { recursive: true }, (eventType, filename) => {
console.log(`${eventType}: ${filename}`);
});
🔗 Para saber más
Algoritmos y estructuras de datos
Array.sort y Timsort
¿Qué es?
Desde V8 7.0, Array.prototype.sort usa Timsort (estable, O(n log n)). Por defecto ordena como strings (convierte elementos a string y compara lexicográficamente). Para ordenación numérica, debes pasar un comparador.
Sintaxis básica
const nums = [3, 1, 4, 1, 5, 9, 2, 6];
// Ordenación por defecto: alfabética
nums.sort(); // [1, 1, 2, 3, 4, 5, 6, 9]
// Peligro:
[10, 2, 1].sort(); // [1, 10, 2] — compara strings: "10" < "2"
// Ordenación numérica con comparador:
nums.sort((a, b) => a - b); // ascendente
nums.sort((a, b) => b - a); // descendente
// Timsort es ESTABLE
const estudiantes = [
{ nombre: "Ana", nota: 85 },
{ nombre: "Luis", nota: 92 },
{ nombre: "Sofia", nota: 85 },
];
estudiantes.sort((a, b) => a.nota - b.nota); // Ana antes que Sofia (estable)
// toSorted (ES2023) — copia, no muta
const copia = nums.toSorted((a, b) => a - b);
🧪 Cómo probarlo
node -e "
const n = [3, 1, 4, 1, 5];
console.log(n.toSorted((a,b) => a-b)); // [1, 1, 3, 4, 5]
console.log(n); // [3, 1, 4, 1, 5] (original intacto)
"
import assert from "node:assert/strict";
assert.deepStrictEqual([3, 1, 4].toSorted(), [1, 3, 4]);
assert.deepStrictEqual([3, 1, 4].toSorted((a, b) => b - a), [4, 3, 1]);
💡 Memoria y rendimiento
- Timsort: O(n log n) peor caso, O(n) en arrays casi ordenados
sort()muta el array original — usatoSorted()para inmutabilidad (ES2023)- Comparador debe ser transitivo: si
a > byb > c, entoncesa > c - ⚠️ Timsort en V8 usa memoria extra O(n) en el peor caso
✅ Buenas prácticas
- Siempre pasa un comparador para números:
(a, b) => a - b - Para strings, usa
localeCompare:(a, b) => a.localeCompare(b) - Usa
toSorted()en lugar desort()cuando no quieras mutar el original - ❌ No asumas que
sort()sin argumentos ordena números correctamente
🏗️ Metodología
- Orden simple de números →
sort((a,b) => a-b) - Orden multi-criterio → comparador compuesto
- Objetos con strings →
localeCompare - Rendimiento crítico → considerar
Intl.Collatorpara strings
🔗 Para saber más
Búsqueda y Big O de colecciones
¿Qué es?
La búsqueda en colecciones varía drásticamente: Set.has() y Map.get() son O(1) por hash; Array.includes() y Array.find() son O(n) lineal. Elegir la estructura correcta es clave para el rendimiento.
Sintaxis básica
// Set.has() es O(1)
const set = new Set([1, 2, 3, 4, 5, 6, 7, 8, 9, 10]);
console.log(set.has(7)); // true, O(1)
// Array.includes() es O(n)
const array = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
console.log(array.includes(7)); // true, O(n)
// Map.get() es O(1)
const map = new Map(array.map((n) => [n, n]));
console.log(map.get(7)); // 7, O(1)
// Binary search manual (array ordenado)
function binarySearch(arr, target) {
let left = 0, right = arr.length - 1;
while (left <= right) {
const mid = Math.floor((left + right) / 2);
if (arr[mid] === target) return mid;
if (arr[mid] < target) left = mid + 1;
else right = mid - 1;
}
return -1;
}
// find, findIndex, findLastIndex
sorted.find((n) => n > 5); // 7
sorted.findIndex((n) => n > 5); // 3
sorted.findLastIndex((n) => n < 10); // 4 (ES2023)
// Object.groupBy (ES2024)
const inventario = [
{ nombre: "manzana", categoria: "fruta" },
{ nombre: "zanahoria", categoria: "verdura" },
];
const porCategoria = Object.groupBy(inventario, (item) => item.categoria);
🧪 Cómo probarlo
node -e "
const s = new Set([1,2,3]);
console.log(s.has(2)); // true
console.log(s.has(5)); // false
"
import assert from "node:assert/strict";
const s = new Set([1, 2, 3]);
assert.ok(s.has(2));
assert.strictEqual(s.has(5), false);
const m = new Map([["a", 1]]);
assert.strictEqual(m.get("a"), 1);
💡 Memoria y rendimiento (Big O)
| Operación | Array | Set | Map | TypedArray |
|---|---|---|---|---|
| Acceso por indice | O(1) | N/A | N/A | O(1) |
Búsqueda .includes() / .has() | O(n) | O(1) | O(1) | O(n) |
Inserción al final .push() | O(1) amortizado | O(1) | O(1) | O(1) |
Inserción al inicio .unshift() | O(n) | N/A | N/A | O(n) |
| Eliminación por valor | O(n) | O(1) | O(1) | O(n) |
| Iteración | O(n) | O(n) | O(n) | O(n) |
Ordenación .sort() | O(n log n) | N/A | N/A | O(n log n) |
✅ Buenas prácticas
- Búsqueda frecuente → Set (valores) o Map (clave-valor), O(1)
- Necesitas orden → Array (Set/Map preservan inserción pero no se reordenan)
- Filtrado eficiente: convierte a Set primero
- ❌ No uses
filter+includespara exclusión (O(n²)) — usa Set
🏗️ Metodología
- Deduplicación → new Set(array)
- Búsqueda rápida con datos planos → Set.has()
- Diccionario con claves arbitrarias → Map
- Ordenación de objetos → sort con comparador
🔗 Para saber más
Conceptos clave explicados a fondo
Promesas, async/await, event loop y microtasks
¿Qué es?
Una promesa representa un valor futuro. async/await es azúcar sintáctico sobre promesas. El event loop es el corazón de la concurrencia en JS: gestiona la ejecución de tareas sincronas, microtasks (promesas) y macrotasks (timers, I/O). JS es single-threaded pero no bloqueante gracias al event loop.
Sintaxis básica
// Promesa
const promesa = new Promise((resolve, reject) => {
setTimeout(() => resolve("Datos cargados"), 1000);
});
promesa.then(console.log).catch(console.error).finally(() => console.log("fin"));
// async/await
async function obtenerUsuario(username) {
const res = await fetch(`https://api.github.com/users/${username}`);
if (!res.ok) throw new Error(`HTTP ${res.status}`);
return await res.json();
}
// Metodos estaticos
Promise.all([p1, p2, p3]); // todas (falla rapido)
Promise.allSettled([p1, p2]); // todas (nunca falla)
Promise.race([p1, p2]); // primera en resolverse
Promise.any([Promise.reject("x"), p2]); // primera exitosa
// Event Loop - microtasks vs macrotasks
console.log("1 sincrono");
setTimeout(() => console.log("2 macrotask"), 0);
Promise.resolve().then(() => console.log("3 microtask"));
console.log("4 sincrono");
// Output: 1, 4, 3, 2
🧪 Cómo probarlo
node -e "
Promise.resolve(42).then(console.log); // 42
"
import assert from "node:assert/strict";
const p = Promise.resolve(42);
assert.strictEqual(await p, 42);
// Event loop order
const order = [];
order.push(1);
setTimeout(() => order.push(3), 0);
Promise.resolve().then(() => order.push(2));
await new Promise(r => setTimeout(r, 10));
assert.deepStrictEqual(order, [1, 2, 3]);
💡 Memoria y rendimiento
- Las promesas tienen overhead frente a callbacks (~2-5x más lentas en creación)
- Pero son mucho más manejables que callback hell
Promise.allejecuta en paralelo (concurrencia), no secuencial- Microtasks tienen prioridad sobre macrotasks — las promesas se ejecutan antes que setTimeout
- ⚠️ Cadenas de promesas muy largas pueden consumir memoria (cada
.then()crea una nueva promesa)
✅ Buenas prácticas
- Usa
async/awaiten lugar de.then()/.catch()(más legible) - Siempre maneja errores con
try/catchalrededor deawait - Usa
Promise.allpara operaciones independientes, no secuenciales - Evita
Promise.allcon arrays gigantes (crea todas las promesas simultáneamente) - ❌ No mezcles callbacks y promesas en el mismo flujo
- ❌ No olvides
awaitdentro deasyncfunctions (la promesa no se resolverá)
🏗️ Metodología
- I/O (HTTP, BD, archivos) → async/await
- Operaciones paralelas independientes → Promise.all
- Operaciones con timeout → Promise.race + AbortController
- Flujos de datos continuos → Async iterables (
for await...of) - Para rate limiting → librería
p-limit
🔗 Para saber más
Closures y scope
¿Qué es?
Un closure es una función que recuerda el ámbito donde fue creada, incluso después de que ese ámbito haya terminado. Es el mecanismo fundamental para encapsulación en JS. El scope determina qué variables son accesibles: global, function (con var) y block (con let/const).
Sintaxis básica
// Closure
function crearContador(inicial = 0) {
let cuenta = inicial; // Variable "privada"
return {
incrementar() { return ++cuenta; },
obtener() { return cuenta; },
};
}
const contador = crearContador(10);
console.log(contador.incrementar()); // 11
// El problema del bucle con var
for (var i = 0; i < 3; i++) {
setTimeout(() => console.log(i), 100); // 3, 3, 3
}
// Solucion: let (block scope)
for (let i = 0; i < 3; i++) {
setTimeout(() => console.log(i), 100); // 0, 1, 2
}
// IIFE (Immediately Invoked Function Expression)
const modulo = (function () {
const privado = "solo aqui dentro";
return { publico: () => privado.toUpperCase() };
})();
// Debounce con closure
function debounce(fn, delay) {
let timer = null;
return function (...args) {
clearTimeout(timer);
timer = setTimeout(() => fn.apply(this, args), delay);
};
}
🧪 Cómo probarlo
node -e "
function crearC() { let c = 0; return () => ++c; }
const c = crearC();
console.log(c()); // 1
console.log(c()); // 2
"
import assert from "node:assert/strict";
function crearC() { let c = 0; return () => ++c; }
const c = crearC();
assert.strictEqual(c(), 1);
assert.strictEqual(c(), 2);
💡 Memoria y rendimiento
- Cada closure mantiene viva la variable capturada en memoria (no se recolecta mientras exista la función)
- Closures en bucles con
varcomparten la misma variable (todas ven el valor final) - Las closures tienen overhead mínimo (~bytes adicionales por variable capturada)
- ⚠️ Closures grandes que capturan objetos pesados pueden causar memory leaks
✅ Buenas prácticas
- Usa closures para encapsular estado privado (patrón módulo)
- Usa
leten bucles para crear closures independientes - Para closures que capturan objetos grandes, asigna
nullcuando ya no se necesiten - ❌ No crees funciones dentro de bucles a menos que sea necesario (cada iteración crea un nuevo closure)
🏗️ Metodología
- Encapsulación de estado → patrón módulo (closure)
- Middleware/pipelines → closures capturan configuración
- Event handlers → closures preservan estado del componente
- Currying/partial application → closures capturan argumentos parciales
- React Hooks →
useState,useEffectusan closures para preservar estado entre renders
🔗 Para saber más
this, bind, call, apply
¿Qué es?
this se determina en tiempo de ejecución según cómo se llama la función, no dónde se define. Las arrow functions no tienen su propio this — heredan del ámbito léxico. bind/call/apply permiten controlar this explícitamente.
Sintaxis básica
// 1. Llamada normal: this → global (o undefined en strict mode)
function mostrar() { "use strict"; console.log(this); // undefined }
// 2. Metodo de objeto: this → el objeto
const obj = { nombre: "obj", mostrar };
obj.mostrar(); // { nombre: "obj", mostrar: f }
// 3. Constructor (new): this → la nueva instancia
new Persona("Ana"); // this → instancia nueva
// 4. Arrow function: hereda this del ambito lexico
const objeto = {
nombre: "objeto",
correcto: function () {
setTimeout(() => console.log(this.nombre), 100); // "objeto"
},
};
// bind, call, apply
function saludar(prefix, suffix) {
return `${prefix} ${this.nombre}${suffix}`;
}
const user = { nombre: "Ana" };
saludar.call(user, "Hola", "!"); // "Hola Ana!"
saludar.apply(user, ["Hola", "!"]); // "Hola Ana!"
const saludarAna = saludar.bind(user, "Hola");
saludarAna("!"); // "Hola Ana!"
🧪 Cómo probarlo
node -e "
const o = { n: 42, f() { return this.n; } };
console.log(o.f()); // 42
"
import assert from "node:assert/strict";
const o = { n: 42, f() { return this.n; } };
assert.strictEqual(o.f(), 42);
💡 Memoria y rendimiento
bind()crea una nueva función (coste de asignación)call/applyson equivalentes en rendimiento; usacallsi conoces los argumentos- Arrow functions no tienen
argumentsninew.target - ⚠️ Perder
thises común al pasar métodos como callbacks:const f = obj.metodo; f()pierdethis
✅ Buenas prácticas
- Usa arrow functions para evitar perder
this(callbacks, event handlers) - Usa
bindpara fijarthispermanentemente en un método - Prefiere
callsobreapplya menos que los argumentos sean dinámicos - ❌ No uses arrow functions para métodos de objetos (pierden
thisdinámico) - ❌ No crees funciones.bind() dentro de renders (React) o bucles — crea una nueva cada vez
🏗️ Metodología
- Métodos de clase → funciones regulares (necesitan
thisdinámico) - Callbacks y closures → arrow functions (heredan
this) - APIs que requieren
thisexplícito →bindla primera vez, luego reusa - En React class components:
.bind(this)en constructor o arrow functions en class fields
🔗 Para saber más
Proxy, Reflect, y metaprogramación
¿Qué es?
Proxy permite interceptar operaciones sobre un objeto (get, set, has, delete, etc.). Reflect proporciona métodos para realizar esas operaciones de forma estándar. Juntos permiten metaprogramación: validación, logging, observabilidad, virtualización.
Sintaxis básica
const objetivo = { mensaje: "hola" };
const proxy = new Proxy(objetivo, {
get(target, prop, receiver) {
if (prop in target) return Reflect.get(target, prop, receiver);
return `Propiedad "${String(prop)}" no existe`;
},
set(target, prop, value) {
if (prop === "edad" && typeof value !== "number") {
throw new TypeError("edad debe ser un numero");
}
return Reflect.set(target, prop, value);
},
has(target, prop) {
console.log(`Verificando "${String(prop)}"`);
return prop in target;
},
deleteProperty(target, prop) {
console.log(`Eliminando "${String(prop)}"`);
return delete target[prop];
},
});
console.log(proxy.mensaje); // "hola"
console.log(proxy.noExiste); // "Propiedad 'noExiste' no existe"
proxy.edad = 30; // OK
🧪 Cómo probarlo
node -e "
const p = new Proxy({}, { get(t, prop) { return prop in t ? t[prop] : 'default'; } });
console.log(p.x); // default
p.x = 42;
console.log(p.x); // 42
"
import assert from "node:assert/strict";
const p = new Proxy({}, { get(t, prop) { return prop in t ? t[prop] : 42; } });
assert.strictEqual(p.anything, 42);
💡 Memoria y rendimiento
- Proxy tiene overhead significativo (cada operación pasa por los traps)
- No uses Proxy en hot paths (bucles estrechos, renders frecuentes)
Reflecttiene rendimiento similar a las operaciones directas- ⚠️ Proxy no es transpilable a ES5 — cuidado con compatibilidad
✅ Buenas prácticas
- Usa Proxy para validación de esquemas, logging, acceso remoto (virtual proxy)
- Usa Reflect para implementar traps de Proxy (siempre usa Reflect.get/set dentro de traps)
- ❌ No abuses de Proxy para getters/setters simples (classes hacen lo mismo sin overhead)
- ❌ Proxy no reemplaza
Object.definePropertypara propiedades específicas
🏗️ Metodología
- Validación de esquemas → Proxy en set trap
- Logging automático → Proxy en get/set traps
- Objetos virtuales (API remota) → Proxy con get que hace fetch
- Observabilidad (Vue 3, MobX) → Proxy detecta cambios en objetos
🔗 Para saber más
POO y patrones de diseño
Clases y prototipos
¿Qué es?
JavaScript implementa POO mediante prototipos (no clases clásicas). Las clases ES6+ son azúcar sintáctico sobre prototipos. Cada objeto tiene un [[Prototype]] (otro objeto del que hereda propiedades). La cadena termina en Object.prototype → null.
Sintaxis básica
// Prototipos
function Animal(nombre) { this.nombre = nombre; }
Animal.prototype.saludar = function () {
return `Hola, soy ${this.nombre}`;
};
const perro = new Animal("Rex");
console.log(perro.saludar()); // "Hola, soy Rex"
// Clases (mismo mecanismo interno)
class Persona {
#edad = 0; // Campo privado (ES2022)
constructor(nombre) { this.nombre = nombre; }
saludar() { return `Hola, soy ${this.nombre}`; }
get edad() { return this.#edad; }
set edad(valor) {
if (typeof valor !== "number") throw new TypeError("edad debe ser numero");
this.#edad = valor;
}
static crear(nombre) { return new Persona(nombre); }
}
console.log(typeof Persona); // "function"
console.log(Persona.prototype.saludar); // [Function: saludar]
// Herencia
class Empleado extends Persona {
constructor(nombre, puesto) {
super(nombre);
this.puesto = puesto;
}
saludar() { return `${super.saludar()} y soy ${this.puesto}`; }
}
🧪 Cómo probarlo
node -e "
class A { constructor(v) { this.v = v; } getV() { return this.v; } }
const a = new A(42);
console.log(a.getV()); // 42
"
import assert from "node:assert/strict";
class A { constructor(v) { this.v = v; } getV() { return this.v; } }
assert.strictEqual(new A(42).getV(), 42);
💡 Memoria y rendimiento
- Los métodos en el prototipo se comparten entre todas las instancias (no se copian)
- Campos privados
#tienen rendimiento similar a propiedades normales instanceofrecorre la cadena de prototipos (O(n) en profundidad de herencia)- ⚠️ No abuses de herencia profunda (> 3 niveles) — perjudica rendimiento y legibilidad
✅ Buenas prácticas
- Prefiere composición sobre herencia (mixins, composición de objetos)
- Usa clases para modelar entidades con estado y comportamiento
- Marca campos privados con
#para encapsulación real (no solo_por convención) - ❌ No creas jerarquías de herencia profundas (> 2-3 niveles)
- ❌ No confundas
instanceofcon type checking (falla entre realms/frames)
🏗️ Metodología
- Entidades del dominio → clases con campos privados y getters/setters
- Comportamiento compartido → composición (mixins o funciones)
- Jerarquías naturales → herencia (Animal → Perro, pero no Forma → Cuadrado → Tablero)
- En React/Vue/Svelte, prefiere hooks/composables sobre clases
🔗 Para saber más
Ejemplo completo POO
// Sistema multimedia con POO completo
const FormatoVideo = Object.freeze({ MP4: "mp4", WEBM: "webm", AVI: "avi" });
class Contenido {
#reproducciones = 0;
constructor(titulo, duracionSegundos, tags = []) {
this.titulo = titulo;
this.duracionSegundos = duracionSegundos;
this.tags = tags;
}
get reproducciones() { return this.#reproducciones; }
get duracionFormateada() {
const min = Math.floor(this.duracionSegundos / 60);
const seg = this.duracionSegundos % 60;
return `${min}:${String(seg).padStart(2, "0")}`;
}
reproducir() { this.#reproducciones++; return `Reproduciendo ${this.titulo}`; }
obtenerInfo() { throw new Error("Metodo abstracto"); }
}
class Audio extends Contenido {
constructor(titulo, duracionSegundos, bitrateKbps, tags = []) {
super(titulo, duracionSegundos, tags);
this.bitrateKbps = bitrateKbps;
}
obtenerInfo() {
return `Audio: ${this.titulo} | ${this.duracionFormateada} | ${this.bitrateKbps}kbps`;
}
}
class Video extends Contenido {
#subtitulos = false;
constructor(titulo, duracionSegundos, resolucion, formato, tags = []) {
super(titulo, duracionSegundos, tags);
this.resolucion = resolucion;
this.formato = formato;
}
get subtitulos() { return this.#subtitulos; }
set subtitulos(valor) {
if (typeof valor !== "boolean") throw new TypeError("subtitulos debe ser booleano");
this.#subtitulos = valor;
}
obtenerInfo() {
return `Video: ${this.titulo} | ${this.resolucion} | ${this.formato}`;
}
}
// Uso
const cancion = new Audio("Bohemian Rhapsody", 354, 320, ["rock"]);
const pelicula = new Video("Inception", 8880, "4K", FormatoVideo.MKV);
pelicula.subtitulos = true;
for (const c of [cancion, pelicula]) {
console.log(c.reproducir());
console.log(c.obtenerInfo());
}
Patrones de diseño
Module Pattern (IIFE + closure)
const RepositorioUsuarios = (function () {
const usuarios = new Map();
let ultimoId = 0;
return {
crear(nombre, email) {
const id = ++ultimoId;
usuarios.set(id, { id, nombre, email });
return id;
},
obtener(id) { return usuarios.get(id); },
listar() { return [...usuarios.values()]; },
get total() { return usuarios.size; },
};
})();
Observer (EventEmitter)
class EventEmitter {
#listeners = new Map();
on(evento, callback) {
if (!this.#listeners.has(evento)) this.#listeners.set(evento, []);
this.#listeners.get(evento).push(callback);
return () => this.off(evento, callback);
}
off(evento, callback) {
const cbs = this.#listeners.get(evento);
if (cbs) { const idx = cbs.indexOf(callback); if (idx >= 0) cbs.splice(idx, 1); }
}
emit(evento, ...args) {
const cbs = this.#listeners.get(evento);
if (cbs) cbs.forEach((cb) => cb(...args));
}
}
Singleton con ESM
// config.js
export const config = new Config(); // ESM: siempre la misma instancia
Factory (registro dinámico)
class Plugin {
static #registry = new Map();
static registrar(nombre, clase) { this.#registry.set(nombre, clase); }
static crear(tipo, ...args) {
const Clase = this.#registry.get(tipo);
if (!Clase) throw new Error(`Plugin '${tipo}' no existe`);
return new Clase(...args);
}
}
Strategy
const ordenarAsc = (datos) => [...datos].sort((a, b) => a - b);
const ordenarDesc = (datos) => [...datos].sort((a, b) => b - a);
class Analizador {
constructor(estrategia) { this.estrategia = estrategia; }
analizar(datos) { return this.estrategia(datos); }
}
| Patron | En JavaScript |
|---|---|
| Module | IIFE + closure, o ESM nativo |
| Observer | EventEmitter, addEventListener |
| Singleton | ESM module (singleton natural), o clase con #instancia |
| Factory | Registro dinámico con Map |
| Strategy | Funciones como ciudadanos de primera clase |
| Chain of Resp. | Middleware (Express, Koa) |
| Adapter | Duck typing |
🔗 Para saber más
Polimorfismo en detalle
JavaScript tiene polimorfismo estructural (duck typing) como mecanismo principal, pero también admite polimorfismo por herencia y por interfaz implícita.
1. Duck typing (el mecanismo principal de JS)
class Perro { sonido() { return "Guau"; } }
class Gato { sonido() { return "Miau"; } }
class Coche { sonido() { return "Brum"; } }
function hacerSonido(cualquierCosa) {
console.log(cualquierCosa.sonido());
}
hacerSonido(new Perro()); // Guau
hacerSonido(new Gato()); // Miau
hacerSonido(new Coche()); // Brum
2. Polimorfismo por herencia
class Forma { area() { throw new Error("Abstracto"); } }
class Circulo extends Forma {
constructor(radio) { super(); this.radio = radio; }
area() { return Math.PI * this.radio ** 2; }
}
class Rectangulo extends Forma {
constructor(ancho, alto) { super(); this.ancho = ancho; this.alto = alto; }
area() { return this.ancho * this.alto; }
}
3. Polimorfismo paramétrico (TypeScript)
interface Imprimible { imprimir(): string; }
class Documento implements Imprimible { imprimir() { return "Documento"; } }
function enviarAImpresion(objeto: Imprimible) { console.log(objeto.imprimir()); }
En JS puro, las interfaces no existen en runtime — todo es duck typing. TypeScript compila a JS y las interfaces desaparecen. Es duck typing con documentación estática.
🔗 Para saber más
Interacción con contenido multimedia
JavaScript tiene APIs nativas en el navegador para gráficos 2D, audio y video, y potentes librerías en Node.js para procesamiento de imágenes y video.
Canvas API (2D drawing)
const canvas = document.getElementById("miCanvas");
const ctx = canvas.getContext("2d");
ctx.fillStyle = "red";
ctx.fillRect(10, 10, 100, 50);
ctx.beginPath();
ctx.arc(200, 200, 50, 0, Math.PI * 2);
ctx.fill();
ctx.fillText("Hola Canvas", 10, 200, "24px Arial");
// Animacion
let x = 0;
function animar() {
ctx.clearRect(0, 0, canvas.width, canvas.height);
ctx.fillRect(x, 100, 50, 50);
x = (x + 2) % canvas.width;
requestAnimationFrame(animar);
}
ImageData (procesamiento de pixeles)
const imageData = ctx.getImageData(0, 0, canvas.width, canvas.height);
const data = imageData.data; // Uint8ClampedArray [R,G,B,A,R,G,B,A,...]
// Escala de grises
for (let i = 0; i < data.length; i += 4) {
const gray = 0.299 * data[i] + 0.587 * data[i+1] + 0.114 * data[i+2];
data[i] = data[i+1] = data[i+2] = gray;
}
ctx.putImageData(imageData, 0, 0);
Web Audio API
const audioCtx = new AudioContext();
const oscillator = audioCtx.createOscillator();
oscillator.type = "sine";
oscillator.frequency.value = 440; // La4
const gainNode = audioCtx.createGain();
gainNode.gain.value = 0.5;
oscillator.connect(gainNode);
gainNode.connect(audioCtx.destination);
oscillator.start();
Sharp (Node.js — procesamiento de imágenes)
npm install sharp
import sharp from "sharp";
await sharp("input.jpg").resize(300, 200).toFile("output.jpg");
await sharp("input.png").webp({ quality: 80 }).toFile("output.webp");
const metadata = await sharp("foto.jpg").metadata();
| Tipo | Libreria | Formatos | Async |
|---|---|---|---|
| Imagenes | Sharp | JPEG, PNG, WebP, AVIF, GIF | ✅ |
| Imagenes | Jimp | JPEG, PNG, BMP, TIFF | ✅ |
| Video | fluent-ffmpeg | MP4, AVI, MOV, MKV | ✅ |
| GIF | gifencoder | GIF | ✅ |
| Canvas | Nativo (navegador) | PNG, JPEG | N/A |
🔗 Para saber más
Bases de datos
ORMs para Node.js
| Libreria | Async | TypeScript | Para que |
|---|---|---|---|
| Prisma | ✅ | ✅ | ORM moderno, schema declarativo, migrations |
| Drizzle ORM | ✅ | ✅ | SQL-like, tipado fuerte |
| Mongoose | ✅ | ✅ | ODM para MongoDB |
| Knex.js | ✅ | ✅ | Query builder SQL |
Prisma (ejemplo completo)
// prisma/schema.prisma
model Usuario {
id Int @id @default(autoincrement())
email String @unique
nombre String
posts Post[]
}
model Post {
id Int @id @default(autoincrement())
titulo String
contenido String?
autor Usuario @relation(fields: [autorId], references: [id])
autorId Int
}
import { PrismaClient } from "@prisma/client";
const prisma = new PrismaClient();
const usuario = await prisma.usuario.create({
data: { email: "ana@email.com", nombre: "Ana" },
});
const posts = await prisma.post.findMany({
where: { publicado: true },
include: { autor: true },
orderBy: { creadoEn: "desc" },
take: 10,
});
🔗 Para saber más
WebSockets y mensajería
WebSocket nativo
// server.js
import { WebSocketServer } from "ws";
const wss = new WebSocketServer({ port: 8080 });
wss.on("connection", (ws) => {
ws.on("message", (data) => {
wss.clients.forEach((client) => {
if (client.readyState === WebSocket.OPEN) client.send(`Echo: ${data}`);
});
});
ws.send("Bienvenido");
});
Socket.IO (con salas)
import { Server } from "socket.io";
const io = new Server(3000, { cors: { origin: "*" } });
io.on("connection", (socket) => {
socket.on("unirse-sala", (sala) => socket.join(sala));
socket.on("mensaje-sala", ({ sala, mensaje }) => io.to(sala).emit("mensaje", mensaje));
});
| Situacion | Recomendación |
|---|---|
| Aplicación simple, Node 21+ | WebSocket API nativa |
| Salas, eventos nombrados, fallback | Socket.IO |
| Microservicios con streaming | ws directamente |
🔗 Para saber más
Concurrencia y paralelismo
JavaScript es single-threaded pero ofrece múltiples modelos de concurrencia.
Event Loop (concurrencia cooperativa)
Para I/O-bound (HTTP, BD, archivos), el event loop es suficiente:
async function descargarMuchos(urls) {
return Promise.all(urls.map((url) => fetch(url).then((r) => r.json())));
}
Web Workers (navegador) — paralelismo real
// worker.js
self.onmessage = (event) => {
let resultado = 0;
for (let i = 0; i < event.data; i++) resultado += Math.sqrt(i);
self.postMessage({ data: resultado });
};
// main.js
const worker = new Worker("./worker.js", { type: "module" });
worker.postMessage(10_000_000);
worker.onmessage = (event) => console.log("Resultado:", event.data);
worker_threads (Node.js)
import { Worker } from "worker_threads";
import { cpus } from "os";
function procesarEnWorkers(numeros) {
const cores = cpus().length;
const chunkSize = Math.ceil(numeros.length / cores);
const workers = Array.from({ length: cores }, (_, i) => {
const chunk = numeros.slice(i * chunkSize, (i + 1) * chunkSize);
return new Promise((resolve, reject) => {
const worker = new Worker("./heavy.mjs", { workerData: chunk });
worker.on("message", resolve);
worker.on("error", reject);
});
});
return Promise.all(workers).then((r) => r.flat());
}
cluster (Node.js) — múltiples procesos
import cluster from "cluster";
import os from "os";
if (cluster.isPrimary) {
os.cpus().forEach(() => cluster.fork());
cluster.on("exit", () => cluster.fork());
} else {
import http from "http";
http.createServer((req, res) => res.end(`Worker ${process.pid}`)).listen(3000);
}
¿Cuál usar?
| Escenario | Modelo |
|---|---|
| I/O-bound (HTTP, BD, archivos) | Event loop + async/await |
| Cálculo intensivo en frontend | Web Workers |
| Cálculo intensivo en backend | worker_threads |
| Comandos del sistema | child_process |
| Maximo rendimiento HTTP | cluster |
🔗 Para saber más
Testing y calidad
Frameworks y herramientas
| Framework | Proposito | Async | CLI |
|---|---|---|---|
| node:test (Node 20+) | Unitario, integración | ✅ | node --test |
| Vitest | Unitario, integración, cobertura | ✅ | npx vitest |
| Jest | Unitario, integración, mocks | ✅ | npx jest |
| Playwright | E2E (navegador) | ✅ | npx playwright test |
| Supertest | Testing de APIs HTTP | ✅ | npx vitest (integración) |
Cómo testear cada concepto
Variables y tipos — aserciones simples:
import assert from "node:assert/strict";
assert.strictEqual(typeof 42, "number");
assert.strictEqual([..."😀"].length, 1);
Control de flujo — cobertura de ramas:
import assert from "node:assert/strict";
function clasificar(edad) {
if (edad < 12) return "niño";
if (edad < 18) return "adolescente";
return "adulto";
}
assert.strictEqual(clasificar(10), "niño");
assert.strictEqual(clasificar(15), "adolescente");
assert.strictEqual(clasificar(30), "adulto");
POO — mocks y fixtures:
import assert from "node:assert/strict";
class MockDB {
constructor() { this.data = []; }
find(id) { return this.data.find(d => d.id === id) ?? null; }
}
const db = new MockDB();
db.data.push({ id: 1, nombre: "test" });
assert.strictEqual(db.find(1).nombre, "test");
Async — test con timeouts:
import assert from "node:assert/strict";
async function fetchWithTimeout(url, ms) {
const controller = new AbortController();
const timer = setTimeout(() => controller.abort(), ms);
try { return await fetch(url, { signal: controller.signal }); }
finally { clearTimeout(timer); }
}
await assert.rejects(
() => fetchWithTimeout("https://httpbin.org/delay/5", 100),
{ name: "AbortError" }
);
File I/O — directorios temporales:
import assert from "node:assert/strict";
import fs from "fs/promises";
import { tmpdir } from "os";
import { join } from "path";
const dir = await fs.mkdtemp(join(tmpdir(), "test-"));
await fs.writeFile(join(dir, "test.txt"), "contenido");
const data = await fs.readFile(join(dir, "test.txt"), "utf8");
assert.strictEqual(data, "contenido");
Tests parametrizados
import assert from "node:assert/strict";
function sumar(a, b) { return a + b; }
const casos = [
[1, 2, 3],
[-1, 1, 0],
[0, 0, 0],
[1.5, 2.5, 4],
];
for (const [a, b, esperado] of casos) {
assert.strictEqual(sumar(a, b), esperado);
}
Property-based testing (fast-check)
npm install -D fast-check
import fc from "fast-check";
import assert from "node:assert/strict";
function ordenar(arr) { return [...arr].sort((a, b) => a - b); }
fc.assert(
fc.property(fc.array(fc.integer()), (arr) => {
const result = ordenar(arr);
// Propiedades que debe cumplir cualquier ordenacion:
assert.strictEqual(result.length, arr.length); // misma longitud
for (let i = 1; i < result.length; i++) {
assert.ok(result[i - 1] <= result[i]); // ordenado
}
})
);
Cobertura y CI
# Node --experimental-coverage (Node 22+)
node --experimental-coverage --test
# Vitest con c8
npx vitest --coverage
# .github/workflows/test.yml
name: Test
on: [push, pull_request]
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with: { node-version: 22 }
- run: npm ci
- run: npm test
- run: npm run lint
🔗 Para saber más
Conceptos avanzados
Web Workers (paralelismo en el navegador)
Los Web Workers ejecutan JavaScript en un hilo separado, permitiendo cálculos intensivos sin bloquear la UI. Se comunican con el hilo principal mediante postMessage.
// 📁 worker.js
self.onmessage = function (event) {
const { tipo, data } = event.data;
if (tipo === "fibonacci") {
function fib(n) { return n <= 1 ? n : fib(n - 1) + fib(n - 2); }
self.postMessage({ tipo: "resultado", data: fib(data) });
}
};
// 📁 main.js
const worker = new Worker("./worker.js", { type: "module" });
worker.postMessage({ tipo: "fibonacci", data: 40 });
worker.onmessage = (event) => console.log("Fibonacci:", event.data.data);
Transferencia de datos eficiente:
// Transferir ArrayBuffer sin copiar (Transferable objects)
const buffer = new ArrayBuffer(1024 * 1024 * 100); // 100MB
worker.postMessage({ buffer }, [buffer]); // buffer se transfiere, no se copia
// El original queda vacio (buffer.byteLength === 0)
Límites:
- No acceso al DOM,
window,document,parent - No
alert(),confirm(),console.log()en algunos workers - Pueden usar
fetch(),IndexedDB,WebAssembly,Canvas(OffscreenCanvas) - Los datos entre workers se serializan con Structured Clone
🔗 Para saber más
WebAssembly (WASM)
WebAssembly es un formato binario de bajo nivel que se ejecuta cerca de la velocidad nativa. JS puede cargar módulos WASM y llamar a sus funciones.
// Cargar modulo WASM
const response = await fetch("modulo.wasm");
const bytes = await response.arrayBuffer();
const { instance } = await WebAssembly.instantiate(bytes, {
env: { jsFunction: (x) => console.log("desde WASM:", x) },
});
console.log(instance.exports.wasmFunction(42));
// WASM + JS con Emscripten
// Compilar C/C++ a WASM: emcc main.c -o main.js -s EXPORTED_FUNCTIONS=['_sumar']
// Esto genera main.wasm + main.js (glue code)
Casos de uso:
- Procesamiento de imágenes/video (Sharp usa libvips compilado a nativo, pero hay versiones WASM)
- Juegos (Unity, Unreal Engine exportan a WASM)
- Cálculo científico, compresión, parsing, cifrado
- Ejecución de código no confiable en sandbox
Limitaciones:
- Sin acceso directo al DOM (debe llamar a JS)
- Sin acceso a APIs del sistema (archivos, red)
- Memoria lineal (un solo ArrayBuffer grande)
- Depuración limitada
🔗 Para saber más
Server Components (React Server Components)
Los Server Components permiten ejecutar componentes de React en el servidor, enviando solo HTML/stream al cliente. Reducen el JavaScript enviado al navegador.
// 📁 ServerComponent.js (ejecutado en servidor, NO en cliente)
import db from "./database";
export default async function PostList() {
const posts = await db.query("SELECT * FROM posts ORDER BY creado DESC LIMIT 10");
return (
<ul>
{posts.map(post => (
<li key={post.id}>
<h2>{post.titulo}</h2>
<p>{post.contenido}</p>
</li>
))}
</ul>
);
}
// 📁 ClientComponent.js (ejecutado en cliente, con interactividad)
"use client";
import { useState } from "react";
export default function LikeButton({ postId }) {
const [likes, setLikes] = useState(0);
return <button onClick={() => setLikes(l => l + 1)}>Like ({likes})</button>;
}
Beneficios:
- Acceso directo a BD, archivos, APIs sin exponer credenciales
- Cero JavaScript enviado al cliente para componentes puramente servidores
- Streaming: el servidor envía HTML progresivamente
- Los Server Components pueden ser async (uso directo de
async/await)
Frameworks que lo implementan:
- Next.js (App Router): Server Components por defecto
- Astro: islas de interactividad (server-first)
- Remix: loader/action en servidor, componente en cliente
🔗 Para saber más
Temporal API (fechas moderna)
Temporal es la nueva API de fechas para JavaScript (Stage 3 en TC39, disponible en polyfill). Reemplaza al obsoleto objeto Date con tipos inmutables, zonas horarias nativas, calendarios y aritmética precisa.
// Temporal sigue siendo Stage 3 - usa polyfill
import { Temporal } from "temporal-polyfill";
// Crear fechas
const ahora = Temporal.Now.plainDateTimeISO();
const fecha = Temporal.PlainDate.from("2026-06-30");
const hora = Temporal.PlainTime.from("14:30:00");
const zdt = Temporal.ZonedDateTime.from({
timeZone: "Europe/Madrid",
year: 2026, month: 6, day: 30, hour: 14,
});
// Aritmetica
fecha.add({ days: 7 }); // 2026-07-07
fecha.subtract({ months: 1 }); // 2026-05-30
fecha.until(Temporal.PlainDate.from("2026-12-25"));
// { days: 178, months: 5, ... }
// Comparacion
Temporal.PlainDate.compare(fecha, Temporal.PlainDate.from("2026-01-01")); // 1 (posterior)
// Zonas horarias
zdt.withTimeZone("America/New_York");
// Duracion
const duracion = Temporal.Duration.from({ hours: 48, minutes: 30 });
duracion.round({ largestUnit: "days", smallestUnit: "hours" });
// => { days: 2, hours: 0, minutes: 30 }
// Formateo
fecha.toLocaleString("es-ES"); // "30/6/2026"
Ventajas sobre Date:
- Inmutable: todas las operaciones devuelven nuevos objetos
- Zonas horarias nativas:
ZonedDateTimecon IANA timezones - Calendarios: soporta calendarios no gregorianos
- Aritmética precisa:
add/subtractrespetan meses de distinta duración - API coherente:
from(),compare(),until(),round()
🔗 Para saber más
Proyecto final integrador
Gestor de contraseñas CLI
Combina file system, cifrado, POO, módulos, y testing en un proyecto ejecutable.
#!/usr/bin/env node
// password-manager.mjs
import { readFileSync, writeFileSync, existsSync } from "fs";
import { randomBytes, createCipheriv, createDecipheriv } from "crypto";
import { createInterface } from "readline";
import { stdin, stdout } from "process";
const ALGORITHM = "aes-256-gcm";
const VAULT_FILE = "vault.enc";
const KEY_FILE = "vault.key";
function getOrCreateKey() {
if (existsSync(KEY_FILE)) return readFileSync(KEY_FILE);
const key = randomBytes(32);
writeFileSync(KEY_FILE, key);
return key;
}
function encrypt(text, key) {
const iv = randomBytes(16);
const cipher = createCipheriv(ALGORITHM, key, iv);
let encrypted = cipher.update(text, "utf8", "hex");
encrypted += cipher.final("hex");
return iv.toString("hex") + ":" + encrypted + ":" + cipher.getAuthTag().toString("hex");
}
function decrypt(text, key) {
const parts = text.split(":");
const decipher = createDecipheriv(ALGORITHM, key, Buffer.from(parts[0], "hex"));
decipher.setAuthTag(Buffer.from(parts[2], "hex"));
return decipher.update(parts[1], "hex", "utf8") + decipher.final("utf8");
}
class Vault {
#key;
constructor() { this.#key = getOrCreateKey(); }
getEntries() {
if (!existsSync(VAULT_FILE)) return {};
const encrypted = readFileSync(VAULT_FILE, "utf8");
if (!encrypted.trim()) return {};
return JSON.parse(decrypt(encrypted, this.#key));
}
saveEntries(entries) {
writeFileSync(VAULT_FILE, encrypt(JSON.stringify(entries), this.#key));
}
add(service, username, password) {
const entries = this.getEntries();
entries[service] = { username, password, creado: new Date().toISOString() };
this.saveEntries(entries);
console.log(`Credenciales para '${service}' guardadas`);
}
get(service) {
const entries = this.getEntries();
if (!entries[service]) return console.log(`'${service}' no encontrado`);
const { username, password } = entries[service];
console.log(`Usuario: ${username}\nPassword: ${password}`);
}
list() {
const services = Object.keys(this.getEntries());
services.forEach(s => console.log(` - ${s}`));
}
}
function ask(query) {
const rl = createInterface({ input: stdin, output: stdout });
return new Promise((resolve) => rl.question(query, (answer) => { rl.close(); resolve(answer); }));
}
async function main() {
const vault = new Vault();
const cmd = process.argv[2];
if (cmd === "add") {
vault.add(await ask("Servicio: "), await ask("Usuario: "), await ask("Password: "));
} else if (cmd === "get") vault.get(process.argv[3] || await ask("Servicio: "));
else if (cmd === "list") vault.list();
else console.log("Comandos: add, get, list");
}
main().catch(console.error);
Tests (password-manager.test.mjs):
import assert from "node:assert/strict";
// Test de cifrado
function encrypt(text, key) {
const { randomBytes, createCipheriv } = await import("crypto");
const iv = randomBytes(16);
const cipher = createCipheriv("aes-256-gcm", key, iv);
let e = cipher.update(text, "utf8", "hex");
e += cipher.final("hex");
return iv.toString("hex") + ":" + e + ":" + cipher.getAuthTag().toString("hex");
}
// Test basico de Vault
const key = Buffer.alloc(32, 0xff);
const encrypted = encrypt("test-data", key);
assert.ok(encrypted.includes(":"), "Debe contener separadores");
assert.ok(encrypted.length > 0, "No debe estar vacio");
Proyecto integrador: funciones
| Componente | Implementación |
|---|---|
| File system | Lectura/escritura de vault.enc y vault.key |
| Algoritmo | AES-256-GCM (cifrado simétrico) |
| POO | Clase Vault con campos privados #key |
| Testing | Tests de cifrado/descifrado |
| Red (opcional) | Compartir vault por red o sincronizar |
🔗 Para saber más
Despliegue a producción
Build para producción
npm run build
Docker multi-stage
FROM node:22-alpine AS builder
WORKDIR /app
COPY package*.json .
RUN npm ci
COPY . .
RUN npm run build
FROM node:22-alpine
WORKDIR /app
COPY --from=builder /app/dist ./dist
COPY --from=builder /app/node_modules ./node_modules
COPY package.json .
CMD ["node", "dist/index.js"]
Hosting
- Vercel — despliegue desde GitHub con cero configuración
- Netlify — integración continua con tu repo
- Railway — conecta el repo y despliega al instante
CI/CD (GitHub Actions)
name: Deploy
on:
push:
branches: [main]
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: "22"
- run: npm ci
- run: npm test
deploy:
needs: test
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: superfly/flyctl-actions@1.5
with:
args: "deploy"
Canales y recursos en español
YouTube
- Midulive — JavaScript, Node.js, fullstack con proyectos prácticos
- HolaMundo — Tutoriales cortos sobre JS, React y tecnologías web
- MoureDev — JavaScript, automatización, proyectos reales
- Fazt — Cursos completos de JS, React, Node.js, TypeScript
- Sergie Code — JavaScript moderno, React, Next.js
- CodigoFacilito — Cursos estructurados de JavaScript
- Carlos Azaustre — JavaScript, React, Node.js con enfoque profesional
- jonmircha — Cursos gratuitos de JavaScript desde cero
- Victor Robles — Desarrollo web con JavaScript y Node.js
Comunidades
- r/javascript (Reddit) — Noticias, discusiones, proyectos
- r/Node (Reddit) — Enfocado en Node.js
- Stack Overflow en español — Etiqueta
javascript - Discord JavaScript en español — Canales de ayuda y proyectos
Repositorios destacados
- airbnb/javascript — Guia de estilo mas popular
- goldbergyoni/nodebestpractices — Buenas practicas Node.js
- trekhleb/javascript-algorithms — Algoritmos en JS
- type-challenges/type-challenges — Retos TypeScript
Newsletters
- JavaScript Weekly
- Node.js Weekly
- 2ality.com — Blog de Axel Rauschmayer
Hacks y tips de productividad
1. console.table() — tablas formateadas
console.table([{ nombre: "Ana", edad: 30 }, { nombre: "Luis", edad: 25 }]);
2. structuredClone() — deep copy nativa (ES2023)
const copia = structuredClone(original); // soporta Date, Map, Set, ArrayBuffer
3. ?? (Nullish coalescing) — solo para null/undefined
const valor = 0;
const r1 = valor || "default"; // "default" (0 es falsy)
const r2 = valor ?? "default"; // 0 (solo null/undefined)
4. ?. (Optional chaining) — navegación segura
const calle = usuario?.perfil?.direccion?.calle; // undefined si falta algo
5. Campos privados # — encapsulación real
class CuentaBancaria { #saldo = 0; depositar(c) { this.#saldo += c; } }
6. Proxy para validación
function crearModeloValidado(esquema) {
return new Proxy({}, { set(t, p, v) {
if (esquema[p]?.required && v == null) throw new Error(`${p} requerido`);
if (esquema[p]?.type === "number" && typeof v !== "number") throw new TypeError();
return Reflect.set(t, p, v);
}});
}
7. Debounce y Throttle
function debounce(fn, delay = 300) {
let timer = null;
return (...args) => { clearTimeout(timer); timer = setTimeout(() => fn(...args), delay); };
}
function throttle(fn, limit = 300) {
let inThrottle = false;
return (...args) => { if (!inThrottle) { fn(...args); inThrottle = true; setTimeout(() => inThrottle = false, limit); } };
}
8. globalThis — objeto global universal
console.log(globalThis === window); // true en navegador
console.log(globalThis === global); // true en Node.js
9. AbortController — cancelar promesas
async function fetchConTimeout(url, ms = 5000) {
const c = new AbortController();
const t = setTimeout(() => c.abort(), ms);
try { return await fetch(url, { signal: c.signal }); }
finally { clearTimeout(t); }
}
10. Intl — internacionalización nativa
new Intl.NumberFormat("es-ES", { style: "currency", currency: "EUR" }).format(1234.5);
// "1.234,50 EUR"
new Intl.DateTimeFormat("es-ES", { dateStyle: "full" }).format(new Date());
🔗 Para saber más
⚠️ Errores comunes
| Error | Causa | Solución |
|---|---|---|
Cannot read properties of undefined (reading 'x') | Accedes a una propiedad de un objeto que es undefined sin comprobar su existencia | Usa optional chaining (obj?.x), inicializa el objeto con valores por defecto, o valida antes de acceder |
SyntaxError: Unexpected token | Sintaxis mal formada: coma colgante, paréntesis sin cerrar, spread mal usado, o sintaxis moderna en entorno legacy | Revisa la línea indicada; si usas ... o ?? en Node viejo, ajusta el target de Babel o actualiza Node |
ReferenceError: X is not defined | Variable usada sin declarar (let, const, var) o fuera de su scope | Declara la variable antes de usarla; comprueba que no esté confundida con un nombre de propiedad o global |
Resultado inesperado con + entre string y número | JavaScript aplica coerción de tipos: "5" + 3 devuelve "53" en vez de 8 | Usa Number(), parseInt() o +variable antes de la operación; o usa template literals para concatenar intencionalmente |
await is only valid in async functions | Se usa await en una función que no está declarada como async | Añade async antes del function o convierte la función a arrow async: const fn = async () => { ... } |
GET ... 404 al usar fetch desde Node.js o SSR | Se intenta acceder a una URL relativa (/api/datos) desde el servidor donde no hay origin | Usa la URL completa (https://midominio.com/api/datos) o import.meta.env.SITE en Astro/Vite para construir la URL absoluta |
| Promesa que nunca se resuelve (memory leak) | Se crea una promesa con new Promise() pero no se llama resolve ni reject en todas las ramas | Asegúrate de que todas las ramas del código asíncrono terminen llamando a resolve o reject; usa Promise.race con timeout para evitar bloqueos |
🔗 Guías relacionadas
Complementa tu conocimiento de JavaScript con estas guías:
- Guía de TypeScript: de 0 a 100 — añade tipado estático a tu JavaScript para evitar muchos de estos errores en tiempo de compilación
- Guía de Astro: de 0 a 100 — usa JavaScript en la práctica con componentes, SSR e Islands
- Guía de Markdown y MDX: de 0 a 100 — entiende el formato base de la mayoría de documentaciones y blogs modernos
Referencias y documentación oficial
- MDN Web Docs: developer.mozilla.org/en-US/docs/Web/JavaScript
- ECMAScript spec: tc39.es/ecma262
- TC39 Proposals: tc39.es/proposals
- Node.js docs: nodejs.org/docs/latest/api
- Node.js Learn: nodejs.org/en/learn
- V8 Blog: v8.dev/blog
- TypeScript Handbook: typescriptlang.org/docs/handbook
- npm docs: docs.npmjs.com
- Deno docs: docs.deno.com
- Bun docs: bun.sh/docs
Fuentes de esta guia
- MDN Web Docs — JavaScript Guide
- MDN Web Docs — JavaScript Reference
- Node.js — Documentación oficial
- JavaScript.info — tutorial moderno de JavaScript
- You Don’t Know JS (YDKJS) — serie de libros
- Eloquent JavaScript — libro gratuito online
- Exploring JS — libros detallados sobre ECMAScript
- Prisma — Documentación oficial
- Fastify — Documentación oficial
- Socket.IO — Documentación oficial
- TypeScript — Documentación oficial
- V8 — Blog oficial
- Node.js Best Practices
- r/javascript
- TC39 — ECMAScript proposals
- Airbnb JavaScript Style Guide
Retos Relacionados
Pon a prueba lo aprendido con estos desafíos:
