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"
Preguntas frecuentes de entrevista 🎯
1. ¿Cuál es la diferencia entre var, let y const?
var: function-scoped, hoisted, mutable. Evitar en código moderno.let: block-scoped (if, for, ), no hoisted, mutable. Preferido para variables.const: block-scoped, no hoisted, inmutable a nivel de referencia (pero el contenido de un objeto sí puede mutarse). Preferido por defecto.
// var: hoisted
console.log(x); // undefined (no error)
var x = 5;
// let/const: no hoisted
console.log(y); // ReferenceError: Cannot access 'y' before initialization
let y = 5;
// const es inmutable a nivel de referencia
const obj = { a: 1 };
obj.a = 2; // ✓ permitido (muta el objeto)
obj = { a: 2 }; // ✗ error: asignación a una constante
2. ¿Qué es una closure? ¿Por qué es importante?
Una closure es una función que "recuerda" el scope léxico donde fue creada. Es importante porque permite encapsulación de variables privadas y es la base de patrones como callbacks, HOCs, Redux reducers.
function crear Contador() {
let count = 0; // privada, solo accesible a través de la closure
return {
incrementar: () => ++count,
obtener: () => count,
};
}
const contador = crearContador();
contador.incrementar(); // 1
// No existe forma de acceder a count directamente desde afuera
3. ¿Cuál es el orden de ejecución en Event Loop?
Call Stack → Microtask Queue → Macrotask Queue
Las microtasks (Promises,
queueMicrotask,MutationObserver) se ejecutan ANTES que las macrotasks (setTimeout, setInterval, I/O).
console.log("1"); // sync: 1
setTimeout(() => console.log("2"), 0); // macrotask
Promise.resolve().then(() => console.log("3")); // microtask
console.log("4"); // sync: 4
// Salida: 1, 4, 3, 2
4. ¿Cuál es la diferencia entre promesas y async/await?
Son equivalentes:
async/awaites sintaxis azúcar sobre promesas. La diferencia es queasync/awaites más legible y permite usartry/catchcomo en código síncrono.
// Promesas
function obtenerApi() {
return fetch('/api')
.then(res => res.json())
.then(data => data)
.catch(err => console.error(err));
}
// Async/Await (equivalente, más legible)
async function obtenerApi() {
try {
const res = await fetch('/api');
const data = await res.json();
return data;
} catch (err) {
console.error(err);
}
}
5. ¿Qué es destructuring y para qué sirve?
Destructuring es extraer valores de arrays u objetos en variables separadas. Sirve para hacer código más legible y conciso.
// Array destructuring
const [primero, segundo, ...resto] = [1, 2, 3, 4];
// Object destructuring
const { nombre, edad, ciudad = "Buenos Aires" } = persona;
// En parámetros (muy usado en React)
function mostrar({ nombre, edad }) { }
// Renombrar (alias)
const { nombreCompleto: nombre } = persona;
6. ¿Qué pasa con this en arrow functions?
Arrow functions NO tienen su propio
this. Heredan elthisdel contexto léxico donde fueron creadas. Esto contrasta con funciones normales dondethisse determina por cómo fueron llamadas.
const obj = {
valor: 42,
normal: function() {
console.log(this.valor); // this es obj
},
flecha: () => {
console.log(this.valor); // this es el objeto global (undefined en módulos)
},
};
obj.normal(); // 42
obj.flecha(); // undefined
7. ¿Qué es Promise.all() vs Promise.allSettled() vs Promise.race()?
Promise.all(): Espera a que todas las promesas se resuelvan O que UNA falle (fail fast).Promise.allSettled(): Espera a que TODAS terminen (resueltas o rechazadas).Promise.race(): Devuelve la primera que termine (resuelva o rechace).
// all: falla si alguna rechaza
Promise.all([p1, p2, p3]) // rechaza si p2 falla
// allSettled: siempre devuelve resultado
Promise.allSettled([p1, p2, p3])
// [{status: 'fulfilled', value: ...}, {status: 'rejected', reason: ...}]
// race: la primera gana
Promise.race([delayedPromise(1000), delayedPromise(500)])
// resuelve en ~500ms
8. ¿Cuál es la diferencia entre interface y type en TypeScript?
interface: para describir contratos de objetos. Extensible conextends, puede mergearse.type: más flexible, puede ser union, intersection. Mejor para alias de tipos.
interface Animal { nombre: string; }
interface Perro extends Animal { raza: string; }
// vs
type Animal = { nombre: string };
type Perro = Animal & { raza: string };
// Merge: solo Interface
interface Usuario { name: string; }
interface Usuario { age: number; } // se mergean
// Usuario = { name: string; age: number }
9. ¿Para qué sirven los Generics en TypeScript?
Generics permiten escribir código reutilizable que trabaja con múltiples tipos. Son especialmente útiles en funciones y estructuras que no dependen de un tipo específico.
function primero<T>(arr: T[]): T | undefined {
return arr[0];
}
const num = primero([1, 2, 3]); // T = number
const str = primero(["a", "b"]); // T = string
const usuario = primero([user1, user2]); // T = Usuario
10. ¿Qué diferencia hay entre any y unknown en TypeScript?
any: desactiva type checking completamente. ¡Evitar!unknown: es seguro. Requiere type narrowing (comprobar el tipo) antes de usar.
let algo: any = "texto";
algo.toUpperCase(); // ✓ permitido (pero unsafe)
let desconocido: unknown = "texto";
desconocido.toUpperCase(); // ✗ error: no sabemos que es string
// Type narrowing
if (typeof desconocido === "string") {
desconocido.toUpperCase(); // ✓ ahora es seguro
}