Saltar al contenido principal

JavaScript & TypeScript 🟢

JavaScript moderno (ES6+)

Variables y scope

// var: function-scoped, hoisted — EVITAR
// let: block-scoped, mutable
// const: block-scoped, inmutable (la referencia, no el contenido)

const nombre = "Juan";
let contador = 0;

// Hoisting: var se "sube" al inicio de la función
console.log(x); // undefined (no error)
var x = 5;

console.log(y); // ReferenceError
let y = 5;

Arrow functions y this

// Arrow functions NO tienen su propio `this`
// Heredan el `this` del contexto donde fueron definidas

class Temporizador {
constructor() {
this.segundos = 0;
}

iniciar() {
// ❌ función normal: `this` es undefined en strict mode
setInterval(function() {
this.segundos++; // Error: `this` no es el Temporizador
}, 1000);

// ✅ arrow function: `this` es el Temporizador
setInterval(() => {
this.segundos++;
}, 1000);
}
}

Destructuring

// Array destructuring
const [primero, segundo, ...resto] = [1, 2, 3, 4, 5];
// primero = 1, segundo = 2, resto = [3, 4, 5]

// Object destructuring
const { nombre, edad, ciudad = "Buenos Aires" } = persona;
// ciudad tiene valor por default si no existe en el objeto

// En parámetros de función
function mostrar({ nombre, edad }) {
console.log(`${nombre} tiene ${edad} años`);
}

// Renombrar al desestructurar
const { nombre: nombreCompleto } = persona;

Spread y Rest

// Spread: expandir iterable
const arr1 = [1, 2, 3];
const arr2 = [...arr1, 4, 5]; // [1, 2, 3, 4, 5]

const obj1 = { a: 1, b: 2 };
const obj2 = { ...obj1, c: 3, b: 99 }; // b se sobreescribe: { a:1, b:99, c:3 }

// Clonar (shallow copy)
const copia = { ...original };
const copiaArr = [...original];

// Rest: agrupar en un array
function sumar(primero, ...resto) {
return primero + resto.reduce((a, b) => a + b, 0);
}

Closures

Una closure es una función que "recuerda" el scope donde fue creada.

function crearContador() {
let count = 0; // esta variable vive en el closure

return {
incrementar: () => ++count,
decrementar: () => --count,
obtener: () => count,
};
}

const contador = crearContador();
contador.incrementar(); // 1
contador.incrementar(); // 2
contador.obtener(); // 2

// Uso práctico: función con configuración
function crearMultiplicador(factor) {
return (numero) => numero * factor;
}

const doble = crearMultiplicador(2);
const triple = crearMultiplicador(3);
doble(5); // 10
triple(5); // 15

Promesas y Async/Await

// Promise: representa un valor futuro
const promesa = new Promise((resolve, reject) => {
setTimeout(() => {
if (Math.random() > 0.5) {
resolve("Éxito");
} else {
reject(new Error("Falló"));
}
}, 1000);
});

// Consumir con .then/.catch
promesa
.then(resultado => console.log(resultado))
.catch(error => console.error(error))
.finally(() => console.log("Terminó"));

// Consumir con async/await (más legible)
async function obtenerDatos() {
try {
const resultado = await promesa;
console.log(resultado);
} catch (error) {
console.error(error);
}
}

// Promise combinators
const [usuarios, productos] = await Promise.all([
fetchUsuarios(),
fetchProductos(),
]);

// Promise.allSettled — no falla si alguna rechaza
const resultados = await Promise.allSettled([p1, p2, p3]);
// resultados = [{status: 'fulfilled', value: ...}, {status: 'rejected', reason: ...}]

// Promise.race — la primera que resuelva o rechace
const primero = await Promise.race([p1, p2]);

Event Loop

Call Stack → Web APIs → Callback Queue → Event Loop → Call Stack

Microtask Queue (Promises): tiene prioridad sobre Macrotask Queue
Macrotask Queue (setTimeout, setInterval): se procesa después
console.log("1");                    // sync → Call Stack

setTimeout(() => console.log("2"), 0); // macrotask → macrotask queue

Promise.resolve().then(() => console.log("3")); // microtask → microtask queue

console.log("4"); // sync → Call Stack

// Orden de salida: 1, 4, 3, 2
// Explicación:
// - "1" y "4" son síncronos (Call Stack)
// - "3" es una microtask (Promise) → se ejecuta antes que la macrotask
// - "2" es una macrotask (setTimeout) → se ejecuta último

Módulos ES

// named exports
export const PI = 3.14159;
export function calcularArea(radio) { return PI * radio ** 2; }
export class Circulo { /* ... */ }

// default export (uno por módulo)
export default function principal() { /* ... */ }

// imports
import { PI, calcularArea } from './matematicas.js';
import { calcularArea as area } from './matematicas.js'; // alias
import * as Mat from './matematicas.js'; // namespace
import principal from './matematicas.js'; // default
import principal, { PI } from './matematicas.js'; // ambos

TypeScript — Fundamentos

// Tipos básicos
let nombre: string = "Juan";
let edad: number = 30;
let activo: boolean = true;
let datos: any = "cualquier cosa"; // evitar any

// Arrays
let numeros: number[] = [1, 2, 3];
let nombres: Array<string> = ["Ana", "Bob"];

// Tuple
let par: [string, number] = ["Juan", 30];

// Union types
let id: string | number = "abc123";
id = 42; // también válido

// Type assertion
const input = document.getElementById("nombre") as HTMLInputElement;

Interfaces y Types

// Interface — para describir la forma de un objeto
interface Usuario {
id: number;
nombre: string;
email: string;
edad?: number; // opcional
readonly createdAt: Date; // solo lectura
}

// Type alias — más flexible
type ID = string | number;
type Coordenadas = { x: number; y: number };
type Direccion = "norte" | "sur" | "este" | "oeste"; // literal union

// Diferencia clave:
// Interface: extensible con `extends` y `implements`, puede mergearse
// Type: puede ser union/intersection, no se puede mergear

interface Animal { nombre: string; }
interface Perro extends Animal { raza: string; }

type AnimalConRaza = Animal & { raza: string }; // intersection type

Generics en TypeScript

// Función genérica
function primero<T>(arr: T[]): T | undefined {
return arr[0];
}

const num = primero([1, 2, 3]); // tipo: number
const str = primero(["a", "b"]); // tipo: string

// Interface genérica
interface Respuesta<T> {
data: T;
status: number;
mensaje: string;
}

type RespuestaUsuario = Respuesta<Usuario>;
type RespuestaLista = Respuesta<Usuario[]>;

// Utility types más usados
type UsuarioParcial = Partial<Usuario>; // todos los campos opcionales
type UsuarioRequerido = Required<Usuario>; // todos requeridos
type SoloNombre = Pick<Usuario, 'nombre' | 'email'>; // solo algunos campos
type SinId = Omit<Usuario, 'id'>; // sin algunos campos
type IdOEmail = keyof Pick<Usuario, 'id' | 'email'>; // "id" | "email"
type IdsRecord = Record<string, number>; // { [key: string]: number }

Enums

// Enum numérico
enum Direccion {
Arriba, // 0
Abajo, // 1
Izquierda, // 2
Derecha, // 3
}

// Enum string (preferido — más legible en debugging)
enum Estado {
Pendiente = "PENDIENTE",
Activo = "ACTIVO",
Inactivo = "INACTIVO",
}

// Alternativa moderna: const assertion (más liviano que enum)
const ESTADO = {
Pendiente: "PENDIENTE",
Activo: "ACTIVO",
Inactivo: "INACTIVO",
} as const;

type EstadoType = typeof ESTADO[keyof typeof ESTADO];
// EstadoType = "PENDIENTE" | "ACTIVO" | "INACTIVO"