TypeScript
Tutorial TypeScript: Control de flujo
Aprende control de flujo en TypeScript con if-else, switch, bucles y control de iteraciones para escribir código eficiente y legible.
Aprende TypeScript y certifícateEstructuras condicionales if-else
Las estructuras condicionales son fundamentales en cualquier lenguaje de programación, ya que permiten ejecutar diferentes bloques de código dependiendo de si una condición se cumple o no. En TypeScript, las estructuras condicionales if-else
funcionan de manera similar a JavaScript, pero con la ventaja adicional del sistema de tipos.
Sintaxis básica
La estructura más simple es el if
, que ejecuta un bloque de código solo cuando la condición evaluada es verdadera:
if (condition) {
// Código que se ejecuta si la condición es true
}
Donde condition
debe ser una expresión que se evalúe como un valor booleano (true
o false
). TypeScript verificará en tiempo de compilación que esta expresión sea compatible con un tipo booleano.
Estructura if-else
Cuando necesitamos ejecutar un bloque alternativo cuando la condición es falsa, utilizamos la estructura if-else
:
if (condition) {
// Código que se ejecuta si la condición es true
} else {
// Código que se ejecuta si la condición es false
}
Ejemplo práctico
let miNumero = 10;
// Pregunta: ¿Es miNumero mayor que 0? (10 > 0)
if (miNumero > 0) { // La respuesta es TRUE (Sí)
console.log("El número es positivo"); // Se mostrara este mensaje
} else {
console.log("El número no es positivo"); // Este mensaje NO se muestra
}
let otroNumero = -5;
// Pregunta: ¿Es otroNumero mayor que 0? (-5 > 0)
if (otroNumero > 0) { // La respuesta es FALSE (No)
console.log("El número es positivo"); // Este mensaje NO se muestra
} else {
console.log("El número no es positivo"); // ¡ESTE mensaje SÍ se muestra!
}
Múltiples condiciones con else-if
El programa revisa la primera condición. Si es true
, ejecuta ese código y se olvida de todo lo demás. Si no fue true
, revisa la segunda condición (else if
). Si esta es true
, ejecuta ese código y se olvida del resto. Y así sigue con cada else if
. Si llega al final y ninguna condición fue true
, entonces (y solo entonces) ejecuta el bloque else
final, si existe.
function getTrafficLightColor(color: string): string {
// Pregunta 1: ¿El color es exactamente "red"?
if (color === "red") {
return "¡Alto!"; // Hara esto y ya (no mira los demás else if/else)
} // Si fue No, sigue a la siguiente pregunta...
// Pregunta 2 (solo si no era "red"): ¿El color es exactamente "yellow"?
else if (color === "yellow") {
return "¡Precaución!"; // Si Sí, haz esto y ya
} // Si fue No, sigue a la siguiente pregunta...
// Pregunta 3 (solo si no era "red" ni "yellow"): ¿El color es exactamente "green"?
else if (color === "green") {
return "¡Adelante!";
} // Si fue No, sigue a la siguiente parte...
// Si NINGUNA de las preguntas anteriores fue "Sí":
else {
return "Color de semáforo inválido";
}
}
console.log(getTrafficLightColor("red")); // Pregunta 1: ¿"red" === "red"? -> Sí. Resultado: "¡Alto!"
console.log(getTrafficLightColor("blue")); // Pregunta 1: No. Pregunta 2: No. Pregunta 3: No. Resultado: Va al final "else" -> "Color inválido"
Condiciones anidadas
También podemos anidar estructuras if
dentro de otras:
function checkEligibility(age: number, hasLicense: boolean): string {
// Primera pregunta (la más importante): ¿Tienes 18 años o más?
if (age >= 18) {
// SI la respuesta a la primera pregunta es "Sí"...
// ...entramos aquí. Ahora, DENTRO de este caso, hacemos la SEGUNDA pregunta:
// Pregunta 2 ¿Tienes licencia? (Recuerda, hasLicense ya es true o false)
if (hasLicense) {
// Este código se ejecuta si la respuesta a la Pregunta 1 fue "Sí" Y la respuesta a la Pregunta 2 fue "Sí".
return "Puedes conducir";
} else {
// Este código se ejecuta si la respuesta a la Pregunta 1 fue "Sí" PERO la respuesta a la Pregunta 2 fue "No".
return "Necesitas obtener una licencia primero";
}
} // Si la respuesta a la PRIMERA pregunta fue "No"...
else {
// ...entramos directamente a este bloque. No importa si tuviera licencia o no, la edad es el primer filtro.
return "Eres demasiado joven para conducir";
}
}
console.log(checkEligibility(20, true)); // Pregunta 1: ¿20>=18? -> Sí. Entra. Pregunta 2: ¿true? -> Sí. Resultado: "Puedes conducir"
console.log(checkEligibility(20, false)); // Pregunta 1: ¿20>=18? -> Sí. Entra. Pregunta 2: ¿false? -> No. Resultado: "Necesitas obtener..."
console.log(checkEligibility(16, false)); // Pregunta 1: ¿16>=18? -> No. Va directo al else principal. Resultado: "Eres demasiado joven..."
Operadores lógicos en condiciones
Podemos combinar múltiples condiciones utilizando operadores lógicos:
- && (AND): La condición completa será Verdadera solo si la parte de la izquierda es Verdadera Y además la parte de la derecha también es Verdadera. Si alguna es Falsa, el resultado es Falso.
- || (OR): La condición completa será Verdadera si la parte de la izquierda es Verdadera O bien la parte de la derecha es Verdadera (o si ambas lo son). Solo si AMBAS son Falsas, el resultado es Falso.
- ! (NOT): Este operador cambia el resultado de una condición. Si algo era Verdadero,
!
lo vuelve Falso. Si algo era Falso,!
lo vuelve Verdadero.
// Ejemplo &&
let tieneDinero = true;
let tieneHambre = true;
if (tieneDinero && tieneHambre) { // ¿Tiene dinero Y ADEMÁS tiene hambre? (true && true) -> true
console.log("Puede comer"); // Este se ejecuta
}
// Ejemplo ||
let esFinDeSemana = false;
let esFestivo = true;
if (esFinDeSemana || esFestivo) { // ¿Es fin de semana O BIEN es festivo? (false || true) -> true
console.log("Puedes descansar"); // Este se ejecuta
}
// Ejemplo !
let estaLloviendo = false;
if (!estaLloviendo) { // ¿NO está lloviendo? (!false) -> true
console.log("Sal a pasear"); // Este se ejecuta
}
Operador ternario
Para condiciones simples, podemos utilizar el operador ternario como alternativa más concisa, es una forma corta de escribir un if-else
simple, cuando lo único que quieres es elegir entre dos valores según si la condición es Verdadera o Falsa
condition ? expressionIfTrue : expressionIfFalse
Ejemplo:
// Usando if-else:
function getStatusIFELSE(isActive: boolean): string {
if (isActive) {
return "Active";
} else {
return "Inactive";
}
}
// Usando el Operador Ternario (la forma corta para este caso):
function getStatusTernario(isActive: boolean): string {
return isActive ? "Active" : "Inactive";
// Se lee: ¿Es 'isActive' Verdadera?
// SI (?), devuelve "Active" (:), SI NO, devuelve "Inactive"
}
Narrowing de tipos con if
Una característica poderosa de TypeScript es el narrowing (estrechamiento) de tipos, imagina que tienes una variable que puede ser de diferentes tipos. El "estrechamiento de tipos" es como decirle a TypeScript: "En este momento, esta variable es específicamente de este tipo".
function printLength(value: string | number): void {
if (typeof value === "string") {
// TypeScript sabe que value es string aquí
console.log(value.length);
} else {
// TypeScript sabe que value es number aquí
console.log(value.toFixed(2));
}
}
printLength("hello"); // 5
printLength(42.123); // 42.12
Comprobación de nulabilidad
Antes de usar una variable, es buena práctica comprobar si tiene un valor real o es null/undefined
.
function greet(name: string | null): string {
if (name === null) {
return "Hello, guest!";
} else {
return `Hello, ${name}!`;
}
}
console.log(greet("John")); // "Hello, John!"
console.log(greet(null)); // "Hello, guest!"
Truthy y Falsy
En Typescript al igual que en JavaScript, algunos valores se consideran "falsos" automáticamente:
false
- Obviamente falso0
- El número cero""
- Un texto vacíonull
- Valor nuloundefined
- Valor no definidoNaN
- "No es un número"
function processValue(value: string | number | null | undefined): string {
if (value) {
// Se ejecuta si value es truthy
return `Processing: ${value}`;
} else {
// Se ejecuta si value es falsy
return "No value to process";
}
}
console.log(processValue("data")); // "Processing: data"
console.log(processValue(0)); // "No value to process"
console.log(processValue(null)); // "No value to process"
Buenas prácticas
- Simplicidad: Mantén las condiciones lo más simples posible.
- Evita negaciones: Las condiciones positivas son más fáciles de entender que las negativas.
- Usa llaves: Aunque para bloques de una sola línea las llaves son opcionales, usarlas siempre mejora la legibilidad y previene errores.
- Considera alternativas: Para múltiples condiciones, evalúa si un
switch
podría ser más apropiado.
// Mejor
if (isValid) {
processData();
}
// Evitar
if (!isInvalid) {
processData();
}
Las estructuras condicionales if-else
son herramientas esenciales para controlar el flujo de ejecución en tus programas TypeScript, permitiéndote crear lógica compleja y tomar decisiones basadas en diferentes condiciones.
Switch case en TypeScript
El switch case es una estructura de control que proporciona una forma elegante de manejar múltiples condiciones cuando necesitamos comparar una variable contra valores específicos. A diferencia de las estructuras if-else
encadenadas, el switch
está optimizado para evaluar una expresión contra varios casos posibles.
Sintaxis básica
La estructura básica de un switch
en TypeScript es:
switch (expression) {
case value1:
// Código a ejecutar si expression === value1
break;
case value2:
// Código a ejecutar si expression === value2
break;
default:
// Código a ejecutar si ningún caso coincide
}
Donde:
- expression: Es la expresión que se evalúa una sola vez
- case value: Define un valor posible para comparar con la expresión
- break: Termina la ejecución del bloque switch
- default: Bloque opcional que se ejecuta cuando ningún caso coincide
Ejemplo simple
function getWeekdayMessage(day: number): string {
switch (day) {
case 1:
return "It's Monday, the week just started";
case 2:
return "It's Tuesday";
case 3:
return "It's Wednesday, halfway there";
case 4:
return "It's Thursday";
case 5:
return "It's Friday, weekend is coming!";
case 6:
case 7:
return "It's weekend!";
default:
return "Invalid day number";
}
}
console.log(getWeekdayMessage(5)); // "It's Friday, weekend is coming!"
console.log(getWeekdayMessage(7)); // "It's weekend!"
En este ejemplo, observa cómo los casos 6 y 7 comparten el mismo bloque de código, una característica útil del switch
.
La importancia del break
Si se omite la sentencia break
al final de un bloque case
(y no hay otra sentencia como return
que salga de la función), la ejecución no se detendrá al finalizar ese bloque. En su lugar, la ejecución continuará secuencialmente hacia el siguiente bloque case
(y subsiguientes), ejecutando su código también, independientemente de si el valor de ese case
coincide o no con la expresión original del switch
. Este comportamiento se denomina "fall-through" (literalmente, "caída" o ejecución secuencial a través de los casos).
function demonstrateFallthrough(value: number): void {
switch (value) {
case 1:
console.log("One");
// Sin break, continúa al siguiente caso
case 2:
console.log("Two");
break;
case 3:
console.log("Three");
break;
}
}
demonstrateFallthrough(1);
// Imprime:
// "One"
// "Two"
Por lo tanto, incluir break;
al final de cada bloque case
(a menos que se desee intencionadamente la caída o que una sentencia como return
ya finalice la ejecución de la función) es crucial para asegurar que el switch
funcione como se espera, ejecutando únicamente el código asociado al case
coincidente.
Casos agrupados
Podemos agrupar casos que comparten la misma lógica:
function getSeasonByMonth(month: number): string {
switch (month) {
case 12:
case 1:
case 2:
return "Winter";
case 3:
case 4:
case 5:
return "Spring";
case 6:
case 7:
case 8:
return "Summer";
case 9:
case 10:
case 11:
return "Autumn";
default:
return "Invalid month";
}
}
console.log(getSeasonByMonth(7)); // "Summer"
Aquí aprovechamos que sin break
, el código sigue cayendo hasta encontrar un return
o break
.
Switch con strings
El switch
también funciona con cadenas de texto, lo que lo hace muy versátil:
function getColorHex(colorName: string): string {
switch (colorName.toLowerCase()) {
case "red":
return "#FF0000";
case "green":
return "#00FF00";
case "blue":
return "#0000FF";
case "black":
return "#000000";
case "white":
return "#FFFFFF";
default:
return "Unknown color";
}
}
console.log(getColorHex("Blue")); // "#0000FF"
console.log(getColorHex("YELLOW")); // "Unknown color"
Switch con expresiones
La expresión evaluada puede ser más compleja que una simple variable:
function getDiscount(age: number, isStudent: boolean): number {
switch (true) {
case age < 12:
return 0.5; // 50% discount for children
case age >= 65:
return 0.3; // 30% discount for seniors
case isStudent:
return 0.2; // 20% discount for students
default:
return 0; // No discount
}
}
console.log(getDiscount(10, false)); // 0.5
console.log(getDiscount(25, true)); // 0.2
console.log(getDiscount(70, false)); // 0.3
En este ejemplo, switch(true)
significa "busca el primer caso que sea verdadero". Es como una forma elegante de escribir varios if-else.
Narrowing de tipos con switch
Al igual que con if
, TypeScript realiza narrowing de tipos dentro de los bloques case
:
function processValue(value: string | number): void {
switch (typeof value) {
case "string":
// TypeScript sabe que value es string aquí
console.log(value.toUpperCase());
break;
case "number":
// TypeScript sabe que value es number aquí
console.log(value.toFixed(2));
break;
}
}
processValue("hello"); // "HELLO"
processValue(42.123); // "42.12"
Switch con enums
Los switch
funcionan especialmente bien con enums en TypeScript:
enum HttpStatus {
OK = 200,
Created = 201,
BadRequest = 400,
Unauthorized = 401,
NotFound = 404,
ServerError = 500
}
function getStatusMessage(status: HttpStatus): string {
switch (status) {
case HttpStatus.OK:
return "Request successful";
case HttpStatus.Created:
return "Resource created successfully";
case HttpStatus.BadRequest:
return "Invalid request parameters";
case HttpStatus.Unauthorized:
return "Authentication required";
case HttpStatus.NotFound:
return "Resource not found";
case HttpStatus.ServerError:
return "Internal server error";
default:
return "Unknown status code";
}
}
console.log(getStatusMessage(HttpStatus.NotFound)); // "Resource not found"
Exhaustividad en switch
Una característica poderosa de TypeScript es la verificación de exhaustividad en los switch
. Cuando se usa con tipos discriminados o uniones, TypeScript puede detectar si has cubierto todos los casos posibles:
type Shape =
| { kind: "circle"; radius: number }
| { kind: "square"; sideLength: number }
| { kind: "rectangle"; width: number; height: number };
function calculateArea(shape: Shape): number {
switch (shape.kind) {
case "circle":
return Math.PI * shape.radius ** 2;
case "square":
return shape.sideLength ** 2;
case "rectangle":
return shape.width * shape.height;
default:
// Esta línea asegura que todos los casos están cubiertos
const _exhaustiveCheck: never = shape;
return _exhaustiveCheck;
}
}
const circle: Shape = { kind: "circle", radius: 5 };
console.log(calculateArea(circle)); // 78.54...
Beneficios de la verificación exhaustiva
La verificación de exhaustividad proporciona seguridad adicional durante el desarrollo:
- Detección temprana de errores: Si se añade un nuevo tipo a la unión, TypeScript generará un error de compilación indicando que no se ha manejado ese caso.
- Documentación implícita: El código comunica claramente que se espera manejar todos los casos posibles.
- Mantenimiento robusto: Obliga a actualizar la lógica de manejo cuando se introducen nuevos tipos.
Cuándo usar switch vs if-else
El switch
es más adecuado cuando:
- Comparas una única variable contra múltiples valores constantes
- Tienes muchas condiciones alternativas (más de 2-3)
- Necesitas agrupar casos que comparten la misma lógica
Por otro lado, if-else
es mejor cuando:
- Evalúas condiciones diferentes no relacionadas con una sola variable
- Tienes pocas alternativas (2-3 condiciones)
- Las condiciones implican rangos o expresiones complejas
// Mejor con switch
function getGrade(score: number): string {
switch (Math.floor(score / 10)) {
case 10:
case 9:
return "A";
case 8:
return "B";
case 7:
return "C";
case 6:
return "D";
default:
return "F";
}
}
console.log(getGrade(95)); // "A"
console.log(getGrade(81)); // "B"
console.log(getGrade(55)); // "F"
La estructura switch
proporciona una sintaxis clara y concisa para manejar múltiples condiciones basadas en una sola expresión, mejorando la legibilidad del código cuando se trabaja con conjuntos de valores discretos.
Bucles for, while y do-while
Los bucles son estructuras fundamentales en programación que permiten ejecutar un bloque de código repetidamente. TypeScript, al igual que JavaScript, ofrece varios tipos de bucles para controlar la iteración sobre datos o ejecutar código mientras se cumpla una condición específica.
Bucle for
El bucle for
es probablemente el más utilizado cuando conocemos de antemano el número de iteraciones que necesitamos realizar. Su sintaxis consta de tres partes: inicialización, condición y expresión final.
for (inicialización; condición; expresión de iteración) {
// Bloque de código que se ejecutará en cada repetición del bucle.
}
- Inicialización: Se ejecuta una única vez al comienzo del bucle, antes de que inicie la primera iteración. Su propósito habitual es declarar e inicializar una variable que actuará como contador o punto de partida para la iteración (ej.
let i = 0;
). - Condición: Se evalúa antes de cada nueva iteración. Si el resultado de esta evaluación es
true
(verdadero), el bloque de código dentro del bucle se ejecuta. Si el resultado esfalse
(falso), el bucle finaliza, y la ejecución del programa continúa con la sentencia que sigue a la estructurafor
. (ej.i < 10;
). - Expresión de Iteración: Se ejecuta automáticamente al finalizar cada iteración completa del bloque de código del bucle. Su función principal es actualizar la variable de control (o el estado relevante para la condición) de manera que, en algún momento, la condición se vuelva falsa y el bucle termine.
Un ejemplo básico:
for (let i = 0; i < 5; i++) {
console.log(`Iteration ${i}`);
}
// Imprime:
// Iteration 0
// Iteration 1
// Iteration 2
// Iteration 3
// Iteration 4
El bucle for
clásico es particularmente adecuado para acceder y procesar elementos de un array basándose en su posición numérica (índice):
const fruits: string[] = ["Apple", "Banana", "Cherry", "Dragon fruit"];
for (let i = 0; i < fruits.length; i++) {
console.log(`Fruit ${i + 1}: ${fruits[i]}`);
}
// Imprime:
// Fruit 1: Apple
// Fruit 2: Banana
// Fruit 3: Cherry
// Fruit 4: Dragon fruit
Bucle for...of
TypeScript incluye el bucle for...of
como una forma más concisa y legible de iterar sobre los elementos (valores) de colecciones que son "iterables". Esto incluye Arrays ([]
), Strings (""
), Mapas (Map
), Conjuntos (Set
), y otros tipos de datos.
Su sintaxis es más sencilla que la del for
clásico, ya que abstrae la gestión explícita de un índice:
for (const elemento of coleccionIterable) {
// Bloque de código que se ejecutará para cada 'elemento' individual de la colección.
}
const colors: string[] = ["red", "green", "blue"];
for (const color of colors) {
console.log(`Color: ${color}`);
}
// Imprime:
// Color: red
// Color: green
// Color: blue
Este bucle es más limpio que el for
tradicional cuando solo necesitamos acceder a los valores y no nos importa el índice.
Bucle for...in
El bucle for...in
itera sobre las propiedades enumerables de un objeto:
const person = { // Objeto con propiedades
name: "Alice",
age: 30,
job: "Developer"
};
// Por cada "key" presente en el objeto "person"
for (const key in person) {
// "key" contendra el nombre de la propiedad como un string ("name", luego "age"...)
// Usamos la clave (el nombre de la propiedad) para acceder a su valor en el objeto.
console.log(`${key}: ${person[key as keyof typeof person]}`);-
}
// Imprime:
// name: Alice
// age: 30
// job: Developer
Nota: En TypeScript, necesitamos usar una aserción de tipo (
keyof typeof person
) para acceder a las propiedades dinámicamente, ya que el compilador no puede inferir el tipo de la propiedad en tiempo de compilación.
Es importante recordar que for...in
no está diseñado para iterar sobre arrays, ya que también incluirá propiedades no numéricas y puede no seguir un orden específico:
const numbers = [10, 20, 30];
numbers.customProperty = "test"; // Propiedad personalizada
for (const key in numbers) {
console.log(key); // Imprime: "0", "1", "2", "customProperty"
}
Bucle while
El bucle while
ejecuta un bloque de código mientras una condición especificada sea verdadera:
while (condición) {
// Código a ejecutar mientras la condición sea true
}
Este bucle es útil cuando no sabemos cuántas iteraciones necesitaremos:
let countdown = 5;
while (countdown > 0) {
console.log(`Countdown: ${countdown}`);
countdown--;
}
console.log("Liftoff!");
// Imprime:
// Countdown: 5
// Countdown: 4
// Countdown: 3
// Countdown: 2
// Countdown: 1
// Liftoff!
Un caso de uso común es procesar datos hasta encontrar cierta condición:
const searchText = "TypeScript";
const textToSearch = "JavaScript is great, but TypeScript adds static typing";
let position = 0;
let found = false;
while (position < textToSearch.length) {
if (textToSearch.substring(position, position + searchText.length) === searchText) {
found = true;
break;
}
position++;
}
console.log(found ? `Found at position ${position}` : "Not found");
// Imprime: Found at position 25
Bucle do-while
El bucle do-while
es similar al while
, pero garantiza que el bloque de código se ejecute al menos una vez, ya que la condición se evalúa después de cada iteración:
do {
// Código a ejecutar al menos una vez
} while (condición);
Este bucle es útil cuando necesitamos ejecutar el código al menos una vez, independientemente de la condición:
let userInput = "";
let attempts = 0;
do {
// Simulamos la entrada del usuario
attempts++;
if (attempts === 3) {
userInput = "quit";
}
console.log(`Attempt ${attempts}: ${userInput || "No input"}`);
} while (userInput !== "quit" && attempts < 5);
// Imprime:
// Attempt 1: No input
// Attempt 2: No input
// Attempt 3: quit
Bucles anidados
Podemos anidar bucles para trabajar con estructuras de datos multidimensionales:
const matrix: number[][] = [
[1, 2, 3],
[4, 5, 6],
[7, 8, 9]
];
for (let i = 0; i < matrix.length; i++) {
let row = "";
for (let j = 0; j < matrix[i].length; j++) {
row += matrix[i][j] + " ";
}
console.log(row);
}
// Imprime:
// 1 2 3
// 4 5 6
// 7 8 9
Optimización de bucles
Para mejorar el rendimiento, especialmente en bucles grandes, podemos aplicar algunas optimizaciones:
- Caching de la longitud: Evita recalcular la longitud en cada iteración
const items = ["a", "b", "c", "d", "e"];
// Menos eficiente
for (let i = 0; i < items.length; i++) {
console.log(items[i]);
}
// Más eficiente
for (let i = 0, len = items.length; i < len; i++) {
console.log(items[i]);
}
- Iteración inversa: En algunos casos, iterar desde el final puede ser más eficiente
const largeArray = new Array(1000).fill(0);
for (let i = largeArray.length - 1; i >= 0; i--) {
// Procesar elementos
}
Bucles y tipos genéricos
TypeScript permite crear bucles con tipos genéricos para mantener la seguridad de tipos:
function processItems<T>(items: T[], processor: (item: T) => void): void {
for (const item of items) {
processor(item);
}
}
const numbers = [1, 2, 3, 4, 5];
processItems(numbers, (num) => {
console.log(num * 2);
});
const names = ["Alice", "Bob", "Charlie"];
processItems(names, (name) => {
console.log(`Hello, ${name}!`);
});
Bucles asíncronos
Para operaciones asíncronas dentro de bucles, podemos usar async/await
:
async function processFilesSequentially(fileUrls: string[]): Promise<void> {
for (const url of fileUrls) {
try {
// Simulamos una operación asíncrona
const data = await fetchFile(url);
console.log(`Processed ${url}: ${data.length} bytes`);
} catch (error) {
console.error(`Error processing ${url}:`, error);
}
}
}
// Función simulada para el ejemplo
async function fetchFile(url: string): Promise<string> {
return new Promise((resolve) => {
setTimeout(() => resolve(`Content of ${url}`), 100);
});
}
// Uso
processFilesSequentially([
"file1.txt",
"file2.txt",
"file3.txt"
]);
Cuándo usar cada tipo de bucle
- for: Cuando conoces el número exacto de iteraciones o necesitas un contador.
- for...of: Para iterar sobre elementos de colecciones iterables de forma simple.
- for...in: Para recorrer propiedades de objetos (no recomendado para arrays).
- while: Cuando no sabes cuántas iteraciones necesitarás y la condición debe evaluarse antes de cada iteración.
- do-while: Cuando necesitas ejecutar el código al menos una vez, independientemente de la condición.
Consideraciones de rendimiento
- Los bucles
for
tradicionales suelen ser los más rápidos para arrays grandes. for...of
es más legible pero ligeramente menos eficiente quefor
.- Evita modificar la colección que estás iterando durante el bucle.
- Para operaciones intensivas, considera usar métodos como
forEach
,map
,filter
yreduce
que pueden ser más declarativos y legibles.
// Ejemplo de métodos funcionales vs bucles
const numbers = [1, 2, 3, 4, 5];
// Usando bucle for
const doubledFor: number[] = [];
for (let i = 0; i < numbers.length; i++) {
if (numbers[i] % 2 === 0) {
doubledFor.push(numbers[i] * 2);
}
}
// Usando métodos funcionales
const doubledFunctional = numbers
.filter(num => num % 2 === 0)
.map(num => num * 2);
console.log(doubledFor); // [4, 8]
console.log(doubledFunctional); // [4, 8]
Los bucles son herramientas esenciales para controlar el flujo de ejecución en tus programas TypeScript. Elegir el tipo de bucle adecuado para cada situación te ayudará a escribir código más eficiente, legible y mantenible.
Control de iteraciones (break, continue)
El control de iteraciones es una parte fundamental de la programación que nos permite modificar el comportamiento normal de los bucles. En TypeScript, disponemos de dos instrucciones principales para este propósito: break
y continue
. Estas instrucciones nos permiten tener un control más preciso sobre cómo se ejecutan nuestros bucles.
Instrucción break
La instrucción break
permite terminar inmediatamente la ejecución de un bucle, saltando a la primera línea de código después del bucle. Es especialmente útil cuando encontramos la condición que buscábamos y no necesitamos seguir iterando.
const numbers = [1, 5, 7, 12, 20, 30];
let foundNumber = -1;
for (let i = 0; i < numbers.length; i++) {
if (numbers[i] > 10) {
foundNumber = numbers[i];
break; // Sale del bucle inmediatamente
}
}
console.log(`First number greater than 10: ${foundNumber}`); // First number greater than 10: 12
En este ejemplo, una vez que encontramos el primer número mayor que 10, no necesitamos seguir buscando, por lo que usamos break
para salir del bucle.
Instrucción continue
La instrucción continue
salta a la siguiente iteración del bucle, omitiendo el resto del código en la iteración actual. Es útil cuando queremos omitir ciertos elementos pero continuar procesando los demás.
const mixedArray = [1, "hello", 3, null, 5, undefined, 7];
const validNumbers: number[] = [];
for (let i = 0; i < mixedArray.length; i++) {
// Omitimos elementos que no son números
if (typeof mixedArray[i] !== "number") {
continue; // Salta a la siguiente iteración
}
validNumbers.push(mixedArray[i] as number);
}
console.log(validNumbers); // [1, 3, 5, 7]
En este caso, usamos continue
para omitir cualquier elemento que no sea un número, procesando solo los valores numéricos.
Uso con bucles anidados
Las instrucciones break
y continue
afectan solo al bucle más interno en el que se encuentran. Para controlar bucles externos, podemos usar etiquetas (labels):
outerLoop: for (let i = 0; i < 3; i++) {
for (let j = 0; j < 3; j++) {
if (i === 1 && j === 1) {
console.log(`Breaking out of both loops at i=${i}, j=${j}`);
break outerLoop; // Sale de ambos bucles
}
console.log(`i=${i}, j=${j}`);
}
}
Este código imprimirá:
i=0, j=0
i=0, j=1
i=0, j=2
i=1, j=0
Breaking out of both loops at i=1, j=1
Patrones comunes con break
Búsqueda eficiente
El patrón de búsqueda es uno de los usos más comunes de break
:
function findUserById(users: Array<{id: number, name: string}>, targetId: number) {
for (const user of users) {
if (user.id === targetId) {
return user; // El return implícitamente actúa como un break
}
}
return null; // Usuario no encontrado
}
const users = [
{id: 1, name: "Alice"},
{id: 2, name: "Bob"},
{id: 3, name: "Charlie"}
];
console.log(findUserById(users, 2)); // {id: 2, name: "Bob"}
Validación con break
Podemos usar break
para salir de un bucle cuando encontramos datos inválidos:
function validateData(data: any[]): boolean {
let isValid = true;
for (const item of data) {
if (item === null || item === undefined) {
isValid = false;
break; // No necesitamos seguir validando
}
}
return isValid;
}
console.log(validateData([1, 2, 3])); // true
console.log(validateData([1, null, 3])); // false
Patrones comunes con continue
Filtrado de elementos
El uso más común de continue
es para filtrar elementos que no cumplen ciertos criterios:
function processEvenNumbers(numbers: number[]): number[] {
const result: number[] = [];
for (const num of numbers) {
if (num % 2 !== 0) {
continue; // Omitimos números impares
}
result.push(num * 2); // Procesamos solo números pares
}
return result;
}
console.log(processEvenNumbers([1, 2, 3, 4, 5, 6])); // [4, 8, 12]
Manejo de casos especiales
Podemos usar continue
para manejar casos especiales sin complicar el flujo principal:
function calculateDiscounts(prices: number[]): number[] {
const discountedPrices: number[] = [];
for (const price of prices) {
// Casos especiales
if (price <= 0) {
discountedPrices.push(0);
continue;
}
if (price >= 1000) {
discountedPrices.push(price * 0.8); // 20% descuento
continue;
}
// Caso normal: 10% descuento
discountedPrices.push(price * 0.9);
}
return discountedPrices;
}
console.log(calculateDiscounts([-10, 50, 1200, 300])); // [0, 45, 960, 270]
Break y continue con while y do-while
Estas instrucciones funcionan igual en bucles while
y do-while
:
let i = 0;
while (i < 10) {
i++;
if (i % 2 === 0) {
continue; // Salta los números pares
}
console.log(i); // Imprime solo números impares
if (i >= 7) {
break; // Termina cuando i llega a 7
}
}
// Imprime: 1, 3, 5, 7
Optimización con break
Podemos usar break
para optimizar algoritmos, evitando iteraciones innecesarias:
function isPrime(num: number): boolean {
if (num <= 1) return false;
if (num <= 3) return true;
// Solo necesitamos verificar hasta la raíz cuadrada
const limit = Math.sqrt(num);
for (let i = 2; i <= limit; i++) {
if (num % i === 0) {
return false; // No es primo, salimos inmediatamente
}
}
return true;
}
console.log(isPrime(17)); // true
console.log(isPrime(15)); // false
Combinando break y continue
En algoritmos más complejos, podemos combinar ambas instrucciones:
function findFirstValidPair(numbers: number[]): [number, number] | null {
for (let i = 0; i < numbers.length; i++) {
// Omitimos números negativos
if (numbers[i] < 0) {
continue;
}
for (let j = i + 1; j < numbers.length; j++) {
// Omitimos números negativos
if (numbers[j] < 0) {
continue;
}
// Si la suma es par, encontramos un par válido
if ((numbers[i] + numbers[j]) % 2 === 0) {
return [numbers[i], numbers[j]];
}
}
}
return null; // No se encontró ningún par válido
}
console.log(findFirstValidPair([1, -2, 3, 4, -5])); // [1, 3]
Alternativas modernas a break y continue
En TypeScript moderno, a menudo podemos reemplazar bucles con break
y continue
por métodos de array más declarativos:
// En lugar de:
const evenNumbers: number[] = [];
for (const num of [1, 2, 3, 4, 5]) {
if (num % 2 !== 0) {
continue;
}
evenNumbers.push(num);
}
// Podemos usar:
const evenNumbersModern = [1, 2, 3, 4, 5].filter(num => num % 2 === 0);
console.log(evenNumbersModern); // [2, 4]
// En lugar de:
let firstEven: number | undefined;
for (const num of [1, 3, 5, 6, 7, 8]) {
if (num % 2 === 0) {
firstEven = num;
break;
}
}
// Podemos usar:
const firstEvenModern = [1, 3, 5, 6, 7, 8].find(num => num % 2 === 0);
console.log(firstEvenModern); // 6
Buenas prácticas
- Usa
break
cuando encuentres lo que buscas y no necesites seguir iterando. - Usa
continue
cuando quieras omitir ciertos elementos pero continuar con el resto. - Considera alternativas funcionales como
filter
,find
,some
oevery
para código más declarativo. - Usa etiquetas con moderación, ya que pueden hacer el código más difícil de seguir.
- Añade comentarios cuando uses
break
ocontinue
en situaciones complejas para explicar la lógica.
Las instrucciones break
y continue
son herramientas poderosas que, cuando se usan correctamente, pueden hacer que tus bucles sean más eficientes y tu código más limpio y expresivo.
Otros ejercicios de programación de TypeScript
Evalúa tus conocimientos de esta lección Control de flujo con nuestros retos de programación de tipo Test, Puzzle, Código y Proyecto con VSCode, guiados por IA.
Funciones
Reto composición de funciones
Reto tipos especiales
Reto tipos genéricos
Módulos
Polimorfismo
Funciones TypeScript
Interfaces
Funciones puras
Reto namespaces
Funciones flecha
Polimorfismo
Operadores
Conversor de unidades
Funciones flecha
Control de flujo
Herencia
Clases
Proyecto validación de tipado
Clases y objetos
Encapsulación
Herencia
Proyecto sistema de votación
Reto genéricos con clases
Inmutabilidad
Interfaces
Funciones de alto orden
Reto map y filter
Control de flujo
Interfaces
Reto funciones orden superior
Herencia y clases abstractas
Reto tipos mapped
Herencia de clases
Reto funciones puras
Variables y constantes
Introducción a TypeScript
Reto testing unitario
Funciones de primera clase
Clases
OOP y CRUD en TypeScript
Interfaces y su implementación
Tipos genéricos
Namespaces
Operadores y expresiones
Proyecto generador de contraseñas
Reto unión e intersección
Encapsulación
Tipos de unión e intersección
Tipos de unión e intersección
Reto hola mundo en TS
Variables y constantes
Funciones puras
Control de flujo
Introducción a TypeScript
Resolución de módulos
Control de flujo
Reto tipos de utilidad
Reto tipos literales y condicionales
Reto exportar e importar
Propiedades y métodos
Tipos de utilidad
Clases y objetos
Tipos de datos, variables y constantes
Proyecto Minigestor de tareas
Operadores
Funciones flecha y contexto
Proyecto Inventario de productos
Funciones
Reto type aliases
Funciones de alto orden
Funciones y parámetros tipados
Tipos literales
Reto enums
Tipos de utilidad
Modificadores de acceso y encapsulación
Polimorfismo
Tipos genéricos
Reto módulos
Tipos literales
Inmutabilidad
Proyecto Generator de datos
Variables y constantes
Funciones de primera clase
Todas las lecciones de TypeScript
Accede a todas las lecciones de TypeScript y aprende con ejemplos prácticos de código y ejercicios de programación con IDE web sin instalar nada.
Introducción A Typescript
Introducción Y Entorno
Instalación Y Configuración De Typescript
Introducción Y Entorno
Tipos De Datos, Variables Y Constantes
Sintaxis
Operadores Y Expresiones
Sintaxis
Control De Flujo
Sintaxis
Funciones Y Parámetros Tipados
Sintaxis
Funciones Flecha Y Contexto
Sintaxis
Enums
Sintaxis
Type Aliases Y Aserciones De Tipo
Sintaxis
Clases Y Objetos
Programación Orientada A Objetos
Interfaces Y Su Implementación
Programación Orientada A Objetos
Modificadores De Acceso Y Encapsulación
Programación Orientada A Objetos
Herencia Y Clases Abstractas
Programación Orientada A Objetos
Polimorfismo
Programación Orientada A Objetos
Decoradores Básicos
Programación Orientada A Objetos
Propiedades Y Métodos
Programación Orientada A Objetos
Inmutabilidad
Programación Funcional
Funciones Puras Y Efectos Secundarios
Programación Funcional
Funciones De Primera Clase
Programación Funcional
Funciones De Alto Orden
Programación Funcional
Conceptos Básicos E Inmutabilidad
Programación Funcional
Funciones De Primera Clase Y Orden Superior
Programación Funcional
Composición De Funciones
Programación Funcional
Métodos Funcionales De Arrays (Map, Filter, Reduce)
Programación Funcional
Tipos Literales Y Tipos Condicionales
Tipos Intermedios Y Avanzados
Tipos Genéricos Básicos
Tipos Intermedios Y Avanzados
Tipos De Unión E Intersección
Tipos Intermedios Y Avanzados
Tipos De Utilidad (Partial, Required, Pick, Etc)
Tipos Intermedios Y Avanzados
Unknown, Never Y Tipos Especiales
Tipos Intermedios Y Avanzados
Tipos Mapped
Tipos Intermedios Y Avanzados
Genéricos Con Clases E Interfaces
Tipos Intermedios Y Avanzados
Módulos
Namespaces Y Módulos
Namespaces
Namespaces Y Módulos
Resolución De Módulos
Namespaces Y Módulos
Exportación E Importación De Módulos
Namespaces Y Módulos
Introducción A Módulos
Namespaces Y Módulos
Testing Unitario En Typescript
Testing
En esta lección
Objetivos de aprendizaje de esta lección
- Comprender cómo usar la declaración
if
para ejecutar código basado en condiciones. - Aprender a utilizar la declaración
if...else
para ejecutar diferentes bloques de código según una condición. - Conocer la estructura de la declaración
switch
para ejecutar diferentes acciones en base a distintas condiciones. - Entender cómo funcionan los bucles
for
,while
ydo...while
para repetir código un número específico de veces o hasta que se cumpla una condición. - Saber cómo utilizar el bucle
for
para iterar sobre una secuencia de valores. - Conocer la diferencia entre el bucle
while
y el bucledo...while
.