Fundamentos de bucles en TypeScript: while, do-while, for, break y continue
Aprenda cómo los bucles while, do-while y for de TypeScript evalúan sus condiciones, por qué el ámbito de las variables de bucle cambia los resultados, y cómo break y continue alteran el flujo.
Fuentes: Mastering Loops in TypeScript: while, do-while, for, break & continue
Los bucles permiten ejecutar el mismo bloque de código repetidamente mientras se cumpla una condición, en lugar de copiar una instrucción una y otra vez. Pueden parecer sencillos, pero la mayoría de los errores en los bucles provienen de tres detalles: cuándo se verifica la condición, dónde se encuentra la variable del bucle y dónde se ubican las instrucciones break o continue dentro del cuerpo del bucle. Esta guía explica los bucles while, do-while y for de TypeScript con pequeños ejemplos cuya salida se puede predecir línea por línea, para que pueda leer y escribir código de iteración sin sorpresas.
Por qué la iteración es mejor que la repetición
Escribir los números del 1 al 10 sin usar un bucle requiere diez llamadas a console.log. Con un bucle, describe la tarea una sola vez y el sistema la repite automáticamente; además, el tamaño del código permanece igual, ya sea que necesites diez o diez mil repeticiones.
El bucle while: primero verificar, luego ejecutar
Un bucle while tiene la estructura más simple de todas:
while (condition) {
// statements
}
La condición se evalúa antes de cada iteración. Mientras sea true, se ejecuta el cuerpo del bucle; en cuanto sea false, este finaliza. Un bucle while funcional necesita tres elementos esenciales:
- un valor inicial para la variable de la que depende la condición;
- la propia condición;
- algo dentro del cuerpo que haga que esa variable se acerque a hacer que la condición sea
false.
Si se omite el tercer elemento, el bucle nunca se detiene, un problema que se aborda más adelante.
Contar de 1 a 10
Aquí se pueden ver los tres elementos: i comienza en 1, el bucle se ejecuta mientras i <= 10, y i++ lo incrementa en cada iteración:
let i: number = 1; // initialization
while (i <= 10) {
console.log(i);
i++;
}
Filtrado de números pares
Para imprimir solo los números pares en ese rango, continúe visitando cada valor pero agregue una verificación dentro del código. El operador módulo % devuelve el resto de una división, por lo que i % 2 == 0 es verdadero exactamente cuando i es par:
let i: number = 1;
while (i <= 10) {
if (i % 2 == 0) {
console.log(i);
}
i++;
}
Existe una forma más eficiente de obtener un resultado similar: omita por completo la verificación y avance dos unidades cada vez. Tenga en cuenta que esta versión comienza en 0, por lo que su salida también incluye a 0:
let i: number = 0;
while (i <= 10) {
console.log(i);
i = i + 2;
}
Salida: 0, 2, 4, 6, 8, 10
Filtrado de números impares
Al invertir la comparación a != 0 se seleccionan los valores impares en su lugar. Comenzar desde 0 no supone problema aquí, ya que 0 es par y queda filtrado:
let i: number = 0;
while (i <= 10) {
if (i % 2 != 0) {
console.log(i);
}
i++;
}
Salida: 1, 3, 5, 7, 9
Estos ejemplos utilizan == y !=; las formas estrictas === y !== suelen ser preferidas porque nunca forzan la conversión de tipos, aunque con dos números el resultado es idéntico.
Cuenta regresiva
Para contar hacia atrás, comience con un valor alto y disminúyalo mientras el valor sea al menos 1:
let i: number = 10;
while (i >= 1) {
console.log(i);
i--;
}
Bucles infinitos y cómo ocurren
Una condición while es simplemente una expresión que evalúa a un valor booleano, por lo que while (true) es perfectamente válido. El bucle a continuación se imprime indefinidamente porque nada lo detiene:
let i: number = 1;
while (true) {
console.log(i);
i++;
// no break, no exit condition this runs forever
}
Más a menudo, los bucles infinitos son accidentales: la variable de control nunca se actualiza o avanza en la dirección incorrecta. Un bucle desbocado bloquea todo lo demás en su hilo, por lo que una pestaña del navegador o un proceso de Node.js parece estar atascado. Un while (true) intencional está bien cuando se combina con una salida explícita, generalmente break (explicado más abajo), por ejemplo en lógicas de “reintentar hasta que tenga éxito”.
El bucle do-while: ejecutarse una vez y luego verificar
Un bucle do-while coloca la condición al final:
do {
// statements
} while (condition);
Dado que la verificación ocurre después del cuerpo, este siempre se ejecuta al menos una vez, independientemente de lo que indique la condición. En contraste, un while simple puede no ejecutarse ni una vez.
Cuando la condición es verdadera desde el principio, un do-while se comporta exactamente como un while:
let i: number = 1;
do {
console.log(i);
i++;
} while (i <= 5);
La diferencia se manifiesta cuando la condición es falsa desde el principio:
let i: number = 10;
do {
console.log(i);
i++;
} while (i <= 5);
Salida: 10
El cuerpo se ejecuta incondicionalmente, imprimiendo 10 y aumentando i a 11. Solo entonces se evalúa i <= 5; como su valor es false, el bucle finaliza después de una iteración. Un while (i <= 5) que comience en 10 no imprimiría nada. Utilice do-while cuando sea necesario ejecutarlo al menos una vez, como para mostrar un mensaje de solicitud al menos una vez.
El bucle for: todo en un único encabezado
El bucle for es el que utilizará con mayor frecuencia, especialmente cuando sabe de antemano cuántas veces debe repetirse o cuánto debe cambiar el contador. Reúne la inicialización, la condición y la actualización en un único encabezado, lo que lo hace más compacto que el equivalente while:
for (initialization; condition; increment/decrement) {
// statements
}
Vale la pena memorizar el orden en que se ejecutan esas partes:
- La inicialización se realiza una sola vez, antes que nada más (por ejemplo
let i = 1). - Se evalúa la condición. Si es
true, se ejecuta el cuerpo del bucle; si esfalse, el bucle finaliza. - Después del cuerpo, se ejecuta la expresión de actualización (por ejemplo
i++). - El control vuelve al paso 2, y el ciclo se repite hasta que la condición sea
false.
En otras palabras, la inicialización ocurre una sola vez, mientras que la condición y la actualización se ejecutan en cada ciclo.
Contar de 1 a 10
for (let i = 1; i <= 10; i++) {
console.log(i);
}
Dos formas de obtener números pares
Igual que con while, puedes avanzar de dos en dos:
for (let i = 2; i <= 10; i += 2) {
console.log(i);
}
o visitar cada valor y filtrarlos dentro del cuerpo del bucle:
for (let i = 2; i <= 10; i++) {
if (i % 2 == 0) {
console.log(i);
}
}
Ambos imprimen la misma secuencia:
2
4
6
8
10
El método de paso a paso realiza menos trabajo porque solo procesa valores pares; el filtrado es más flexible cuando la regla no es un paso fijo, como “divisible entre 3 o 7”.
Conteo regresivo
Una actualización que disminuye el valor hace que el bucle se ejecute en orden inverso:
for (let i = 10; i >= 1; i--) {
console.log(i);
}
Salida: 10, 9, 8, 7, 6, 5, 4, 3, 2, 1
Por qué la variable del bucle es 6 después del bucle
El lugar donde se declara la variable del bucle determina qué valor queda después de que el bucle finalice. En este ejemplo, i se declara antes del bucle, y la cabecera for solo le asigna un valor:
let i: number;
for (i = 1; i <= 5; i++) {
console.log(i);
}
console.log(i);
Salida: 1, 2, 3, 4, 5, 6
Dado que i se encuentra en el ámbito exterior, la última llamada a console.log(i) aún puede acceder a él, conservando el valor que dejó la bucle. Ese valor es 6 y no 5, debido al orden de las operaciones mencionado anteriormente: en la última iteración i tiene el valor 5, el cuerpo de la bucle lo imprime, la actualización lo eleva a 6, y solo entonces falla la condición 6 <= 5 y finaliza la bucle.
El estilo más común, for (let i = 1; i <= 5; i++), otorga a i un ámbito de bloque limitado al interior de la bucle, por lo que deja de existir después de este. Referirse a él tras finalizar la bucle genera un error en tiempo de compilación en TypeScript (“Cannot find name 'i'”), mientras que en JavaScript puro provocaría un ReferenceError en tiempo de ejecución. Por lo general, es mejor declararlo al inicio; úsese una declaración exterior solo cuando sea necesario obtener su valor final.
Cambiar el flujo con break y continue
Dos instrucciones de salto permiten modificar el flujo normal de un bucle desde su interior:
breakfinaliza el bucle de inmediato, sin importar cuántas iteraciones queden por realizar.continueabandona solo la iteración actual y pasa a la siguiente; el bucle sigue ejecutándose.
En un bucle for, continue sigue ejecutando la expresión de actualización antes de volver a verificar la condición, por lo que el contador sigue avanzando.
break: la posición dentro del cuerpo del bucle es importante
Aquí la verificación de break ocurre antes de la impresión:
let i: number;
for (i = 1; i <= 10; i++) {
if (i == 5) {
break;
}
console.log(i);
}
Salida: 1, 2, 3, 4
Cuando i llega a 5, el bucle termina antes de que console.log tenga la oportunidad de ejecutarse, por lo que el 5 nunca aparece.
Si se coloca la impresión antes de la verificación, el resultado cambia:
let i: number;
for (i = 1; i <= 10; i++) {
console.log(i);
if (i == 5) {
break;
}
}
Salida: 1, 2, 3, 4, 5
La condición no ha cambiado, pero ahora aparece 5 porque la impresión se realiza antes de la verificación. Tanto con break como con continue, el orden de las instrucciones dentro del cuerpo determina la salida.
continue: saltarse un único valor
Sustituir break por continue convierte “detenerse en 5” en “saltarse 5”:
let i: number;
for (i = 1; i <= 10; i++) {
if (i == 5) {
continue;
}
console.log(i);
}
Salida: 1, 2, 3, 4, 6, 7, 8, 9, 10
El bucle continúa; simplemente se omite la impresión en esa iteración.
Saltar todos los números impares
Combinado con un filtro, continue envía cada valor impar directamente a la siguiente iteración:
let i: number;
for (i = 0; i <= 10; i++) {
if (i % 2 != 0) {
continue;
}
console.log(i);
}
Salida: 0, 2, 4, 6, 8, 10
Saltar una lista de valores específicos
La condición puede ser tan compleja como lo necesite. Al utilizar el operador lógico OR ||, este bucle omite los valores 3, 5 y 7:
let i: number;
for (i = 0; i <= 10; i++) {
if (i == 3 || i == 5 || i == 7) {
continue;
}
console.log(i);
}
Salida: 0, 1, 2, 4, 6, 8, 9, 10
Para una lista de valores a omitir más larga, [3, 5, 7].includes(i) o un Set son opciones más adecuadas.
Puntos clave
whileverifica su condición antes de cada iteración y puede no ejecutarse en absoluto;do-whilela verifica después y siempre se ejecuta al menos una vez.- Cada bucle necesita una actualización que eventualmente haga que la condición sea falsa; de lo contrario, se generará un bucle infinito que bloqueará el hilo.
- En un bucle
for, la inicialización se ejecuta una sola vez, mientras que la condición y la actualización se ejecutan en cada ciclo, por lo que un contador termina un paso más allá del límite.
let en la cabecera del bucle lo mantiene dentro de ese bucle; declararlo fuera permite que su valor final se conserve.break termina el bucle y continue salta una iteración; la posición en que se coloquen en relación con otras instrucciones afecta el resultado.Las estructuras condicionales determinan qué código se ejecuta; los bucles deciden con qué frecuencia. Juntos, abarcan la mayor parte del flujo de control que escribirá.
Lecturas relacionadas
- Fundamentos de TypeScript: Desde la primera anotación hasta generics y modo estricto — Un recorrido estructurado por el sistema de tipos de TypeScript, desde primitivos e inferencia hasta uniones discriminadas, generics, tipos útiles y un tsconfig estricto.