Operadores y control de flujo

Lección 3 de 8. Decidir y repetir en JavaScript: if, switch y los bucles se parecen a los de cualquier lenguaje con llaves, pero cada uno tiene su detalle. Y al final, los tres operadores modernos que más código ahorran: ??, ?. y ??=.

Decidir: if, else y el ternario

El if de JavaScript se escribe como en C o en Java: la condición entre paréntesis y el bloque entre llaves. Lo que entra en la condición no tiene por qué ser un booleano: se aplican las reglas de valores verdaderos y falsos de la lección 2.

function calificacion(nota) {
  if (nota >= 9) {
    return "sobresaliente";
  } else if (nota >= 7) {
    return "notable";
  } else if (nota >= 5) {
    return "aprobado";
  } else {
    return "suspenso";
  }
}

for (const n of [9.5, 7, 5, 3.2]) {
  console.log(n, "->", calificacion(n));
}
salida9.5 -> sobresaliente
7 -> notable
5 -> aprobado
3.2 -> suspenso

Cuando solo hay que elegir entre dos valores, el operador ternario condición ? si : no lo dice en una línea. Úsalo para valores, no para encadenar decisiones: tres ternarios anidados se leen peor que el if de arriba.

const stock = 0;
const etiqueta = stock > 0 ? `quedan ${stock}` : "agotado";
console.log(etiqueta);
salidaagotado

switch, y la caída que nadie espera

switch compara un valor con varios casos. Tiene una particularidad heredada de C: cuando entra en un caso, sigue ejecutando los siguientes hasta encontrar un break. Si se te olvida, pasa esto:

function plan(dia) {
  const tareas = [];
  switch (dia) {
    case "lunes":
      tareas.push("reunión");
    case "martes":
      tareas.push("código");
    case "miércoles":
      tareas.push("revisión");
      break;
    default:
      tareas.push("descanso");
  }
  return tareas;
}

console.log(plan("martes"));
console.log(plan("domingo"));
salida[ 'código', 'revisión' ]
[ 'descanso' ]

Un switch sin break entra en el caso martes y sigue en el siguiente hasta encontrar un breakswitch sin break: entra en un caso y sigue cayendocase «lunes»:sin breakcase «martes»:sin breakcase «miércoles»:break;default:valor = «martes»salida del switchejecuta «martes» y «miércoles»; el break del miércoles lo saca

El martes ejecuta su caso y el del miércoles, porque falta el break; solo se detiene al llegar al break del miércoles. Esquema: decodigo.com.

A veces la caída se usa a propósito, para que varios casos compartan el mismo código. Es legítimo si los casos van seguidos y sin nada en medio:

function tipoDia(dia) {
  switch (dia) {
    case "sábado":
    case "domingo":
      return "fin de semana";
    default:
      return "laborable";
  }
}
console.log(tipoDia("domingo"), "|", tipoDia("lunes"));
salidafin de semana | laborable
switch compara con ===. Un "1" que llega de un formulario no entra en case 1:, porque una cadena nunca es estrictamente igual a un número. Convierte antes con Number(valor).
const valor = "1";       // lo que llega de un <input>
switch (valor) {
  case 1:
    console.log("uno");
    break;
  default:
    console.log("ningún caso:", typeof valor);
}
salidaningún caso: string

Bucles

for, while y do…while

El for clásico tiene tres partes: inicio, condición y paso. Úsalo cuando necesites el índice o un paso distinto de uno; para recorrer una colección hay algo mejor, justo debajo.

for (let i = 10; i > 0; i -= 3) {
  console.log("cuenta atrás:", i);
}

let intentos = 0;
while (intentos < 3) {
  intentos++;
}
console.log("intentos:", intentos);
salidacuenta atrás: 10
cuenta atrás: 7
cuenta atrás: 4
cuenta atrás: 1
intentos: 3

do...while es como while, pero comprueba la condición al final, así que el cuerpo se ejecuta al menos una vez. Se usa poco: para pedir un dato hasta que sea válido, más o menos.

for…of frente a for…in

Son los dos que más se confunden, y la diferencia es sencilla: for...of recorre valores; for...in, claves.

const lista = ["pan", "leche", "café"];
lista.extra = "¿y esto?";

for (const v of lista) console.log("of:", v);
for (const k in lista) console.log("in:", k, typeof k);
salidaof: pan
of: leche
of: café
in: 0 string
in: 1 string
in: 2 string
in: extra string

for…of recorre los valores de un array y for…in sus claves como texto, incluida una propiedad añadidaEl mismo array, recorrido de dos formas«pan»clave 0«leche»clave 1«café»clave 2extraañadida a manofor (const v of lista)los VALORES, en orden«pan»«leche»«café»para arrays, cadenas, Map y Setfor (const k in lista)las CLAVES, como texto«0»«1»«2»«extra»para objetos; en arrays, no

for...of da los valores del array. for...in da las claves, como cadenas, y además cualquier propiedad que alguien haya añadido. Esquema: decodigo.com.

Dos detalles explican por qué for...in no sirve para arrays: las claves llegan como texto ("0" + 1 da "01", no 1) y aparecen propiedades que no son elementos. Para un array, for...of; y si necesitas también el índice, entries():

const lista = ["pan", "leche", "café"];
for (const [i, v] of lista.entries()) {
  console.log(i + 1, v);
}

const precios = { pan: 1.2, leche: 0.95 };
for (const [producto, precio] of Object.entries(precios)) {
  console.log(`${producto}: ${precio} USD`);
}
salida1 pan
2 leche
3 café
pan: 1.2 USD
leche: 0.95 USD

Para los objetos, Object.entries() con for...of es la forma más clara: da cada par clave y valor, y los corchetes de const [producto, precio] los separan en dos variables. Es la desestructuración, que se ve a fondo en la lección 5. for...of también recorre cadenas, letra a letra, y los Map y Set.

break, continue y etiquetas

break sale del bucle y continue salta a la siguiente vuelta. Los dos actúan sobre el bucle más interno. Para salir de dos bucles anidados a la vez, se le pone una etiqueta al de fuera:

const tablero = [
  [0, 0, 0],
  [0, 7, 0],
  [0, 0, 7],
];

buscar: for (let f = 0; f < tablero.length; f++) {
  for (let c = 0; c < tablero[f].length; c++) {
    if (tablero[f][c] === 0) continue;
    console.log(`primer 7 en la fila ${f}, columna ${c}`);
    break buscar;
  }
}
salidaprimer 7 en la fila 1, columna 1

Sin la etiqueta, el break solo saldría del bucle de las columnas y la búsqueda seguiría por la fila siguiente, encontrando también el segundo 7. Si te ves usando muchas etiquetas, suele ser señal de que ese trozo debería ser una función con un return.

&& y || devuelven valores, no booleanos

En JavaScript, a || b no devuelve true o false: devuelve a si es verdadero y, si no, b. Y a && b devuelve a si es falso y, si no, b. Los dos se detienen en cuanto saben el resultado, así que la parte derecha puede no llegar a ejecutarse. Por eso se han usado siempre para poner valores por omisión:

const nombre = "";
console.log(nombre || "anónimo");

const usuario = { admin: true };
usuario.admin && console.log("mostrar el panel de administración");

const puerto = 0;                 // 0 es un puerto válido aquí
console.log("puerto:", puerto || 8080);
salidaanónimo
mostrar el panel de administración
puerto: 8080

La última línea es el problema: el usuario eligió el puerto 0 y || lo cambió por 8080, porque 0 es un valor falso. Lo mismo pasa con una cadena vacía o con false que sí querías conservar. Para esto existe un operador más preciso.

?? : solo null y undefined

El operador ?? (fusión de nulos) devuelve la parte derecha solo si la izquierda es null o undefined. Un 0, una cadena vacía o un false se respetan:

const ver = (v) => (typeof v === "string" ? `"${v}"` : String(v));
const valores = [undefined, null, 0, "", false, NaN, "hola"];

for (const x of valores) {
  console.log(ver(x).padEnd(10), "||", ver(x || "def").padEnd(7), "??", ver(x ?? "def"));
}
salidaundefined  || "def"   ?? "def"
null       || "def"   ?? "def"
0          || "def"   ?? 0
""         || "def"   ?? ""
false      || "def"   ?? false
NaN        || "def"   ?? NaN
"hola"     || "hola"  ?? "hola"

Tabla que compara x || «def» con x ?? «def» para siete valores de xx || «def» frente a x ?? «def»valor de xx || «def»x ?? «def»undefined«def»«def»null«def»«def»0«def»0«»«def»«»false«def»falseNaN«def»NaN«hola»«hola»«hola»|| sustituye todo lo falso; ?? solo null y undefined (recuadros: donde no coinciden)

Con ||, todo lo falso se sustituye. Con ??, solo null y undefined: el 0, la cadena vacía, false y NaN se quedan como están. Esquema: decodigo.com.

La regla práctica: para valores por omisión, ??. Usa || solo cuando de verdad quieras tratar igual cualquier valor falso.

Una precaución: JavaScript no te deja mezclar ?? con || o && sin paréntesis, porque el orden sería ambiguo. Es un error al cargar el archivo, antes de ejecutar nada:

const a = null, b = 0;
const r = a || b ?? "def";
salidaSyntaxError: Unexpected token '??'

Con paréntesis, (a || b) ?? "def", funciona y queda claro qué querías.

?. : el encadenamiento opcional

Este es el error más frecuente de JavaScript, el que verás cientos de veces: leer una propiedad de algo que es undefined.

const usuario = { nombre: "Ana" };     // no tiene dirección
console.log(usuario.direccion.ciudad);
salidaTypeError: Cannot read properties of undefined (reading 'ciudad')

usuario.direccion no existe, así que vale undefined, y pedirle .ciudad a undefined rompe el programa. El mensaje dice exactamente eso: no se pueden leer propiedades de undefined, y entre paréntesis, cuál se intentaba leer. Con ?., si lo de la izquierda es null o undefined, la expresión entera se corta y da undefined, sin error:

const ana = { nombre: "Ana" };
const luis = { nombre: "Luis", direccion: { ciudad: "Lima" }, saludar() { return "hola"; } };

console.log(ana.direccion?.ciudad);
console.log(luis.direccion?.ciudad);
console.log(ana.direccion?.ciudad.toUpperCase());     // se corta antes de llegar aquí
console.log(ana.saludar?.(), "|", luis.saludar?.());
console.log(ana.pedidos?.[0]);
console.log(ana.direccion?.ciudad ?? "ciudad desconocida");
salidaundefined
Lima
undefined
undefined | hola
undefined
ciudad desconocida

Fíjate en la tercera línea: ?. corta toda la cadena, así que .toUpperCase() ni se intenta. Funciona también para llamar a un método que puede no existir, obj.metodo?.(), y para índices, lista?.[0]. Y combinado con ??, da un valor por omisión en una sola línea.

No lo pongas en todas partes. Si una propiedad tiene que existir, un ?. esconde el fallo: el programa sigue con un undefined que acabará dando un error mucho más lejos y más difícil de rastrear. Úsalo donde la ausencia sea normal, como un campo opcional de un formulario o de una respuesta JSON.

Asignaciones lógicas: ||=, &&= y ??=

Son la versión corta de «asigna solo si…». La más útil es ??=, que rellena lo que falta sin pisar lo que ya hay:

const config = { puerto: 0, modo: "" };

config.puerto ??= 8080;     // ya tiene un valor (0): no lo toca
config.host ??= "localhost";
config.modo ||= "producción";

console.log(config);
salida{ puerto: 0, modo: 'producción', host: 'localhost' }

El error frecuente: = dentro del if

Un solo signo igual asigna; tres comparan. Dentro de un if, la asignación es válida y devuelve el valor asignado, así que el programa no da ningún error: simplemente hace otra cosa.

let intentos = 3;
if (intentos = 0) {
  console.log("sin intentos");
} else {
  console.log("aún quedan... ¿o no?", intentos);
}
salidaaún quedan... ¿o no? 0

La condición asignó 0 a intentos y, como 0 es falso, fue por el else, que encima muestra el valor ya destrozado. Compara siempre con ===; los editores con ESLint marcan este caso con la regla no-cond-assign. Hay más sorpresas de este tipo, como el return seguido de un salto de línea, en la página de rarezas de JavaScript.

Resumen

ParaUsaCuidado con
Elegir entre dos valoresc ? a : banidar ternarios
Muchos casos de un mismo valorswitchel break olvidado y que compara con ===
Recorrer un array, una cadena, un Mapfor...of—
Recorrer las claves de un objetoObject.entries() con for...offor...in en arrays
Valor por omisión?? y ??=|| se come el 0 y la cadena vacía
Algo que puede faltar?.usarlo donde algo debe existir

Todo el código de esta lección se ejecutó con Node.js 24 en un contenedor limpio antes de publicarla; las salidas y los errores están copiados de esa ejecución.