learning-front

Nivel 2 · JavaScript: fundamentos del lenguaje

Valores, referencias y comparaciones

Valor vs referencia, igualdad estricta y coerción: por qué dos cosas que parecen iguales a veces no lo son.

En el capítulo anterior aprendiste qué tipos de valores existen en JavaScript. Ahora toca una pregunta que parece simple pero que está detrás de un número sorprendente de bugs: ¿cuándo son dos cosas iguales?

La respuesta depende de si usas el operador correcto, de si los tipos coinciden y de si estás comparando primitivos o referencias. Vamos por partes.

Igualdad estricta (===) vs igualdad débil (==)#

JavaScript tiene dos operadores de igualdad y conviene entender la diferencia desde el principio para usar siempre el correcto.

=== compara valor y tipo sin hacer ninguna conversión previa. Si los tipos no coinciden, devuelve false directamente.

== compara después de convertir ambos operandos al mismo tipo. Esas conversiones siguen unas reglas que, en la práctica, resultan difíciles de memorizar y producen resultados que pocos esperan:

javascript
// false — distinto tipo, sin conversión
'120' === 120
// true  — == convierte '120' a número antes de comparar
'120' == 120

// true  — string vacío se convierte a 0
'' == 0
// true  — esta es "la trampa clásica"
null == undefined
// true  — dos conversiones encadenadas
'0' == false

La regla de empresa es clara: usa siempre ===. El pequeño esfuerzo de asegurarte de que los tipos ya coinciden antes de comparar elimina una categoría entera de bugs.

Desigualdad: !== y !=#

Lo mismo aplica al comprobar que dos cosas no son iguales. JavaScript tiene dos operadores de desigualdad:

  • !== compara valor y tipo, igual que ===, pero devuelve true cuando son diferentes.
  • != aplica coerción antes de comparar, igual que ==, con las mismas sorpresas.
javascript
// true  — distinto tipo: string '120' vs número 120
'120' !== 120
// false — == coerciona antes: '120' se convierte a 120 y coincide
'120' != 120

// Ejemplo con el héroe:
const rol = 'Tanque';
// true  — 'Tanque' es distinto de 'Apoyo' en valor Y tipo
console.log('Roles distintos: ' + (rol !== 'Apoyo'));

La regla es la misma: usa !==, nunca !=. Evitas la misma categoría de bugs que evitas usando === en lugar de ==.

Operadores de comparación#

<, >, <=, >= funcionan con números y, en el caso de strings, los ordenan alfabéticamente. No tienen versión “débil” y “estricta”: solo hay una.

javascript
// true  — 0.65 > 0.6
78 / 120 > 0.6
// true  — 0.48 < 0.6
72 / 150 < 0.6
// true  — mayor O igual
78 / 120 >= 0.6
// true  — menor O igual
72 / 150 <= 0.5

// Con strings: ordenación alfabética, no numérica.
// true  — 'T' va antes que 'W' en el alfabeto
'Tracer' < 'Widowmaker'
// true  — 'G' va después de 'A'
'Genji'  > 'Ana'

En código real, antes de hacer una comparación numérica con datos que vienen de fuera (de un input, de una API), te aseguras de que el tipo sea correcto.

Coerción de tipos#

JavaScript convierte los tipos de forma implícita en determinadas situaciones. El operador + es el caso más llamativo: si uno de los operandos es un string, el otro se convierte a string y se concatenan.

javascript
// '53'  — concatenación, no suma
'5' + 3
// 2     — resta sí fuerza conversión numérica
'5' - 3

Esto importa en el Team Builder porque los valores de los <input> de HTML son siempre strings, aunque el usuario haya escrito un número. Si calculas victorias / partidas con strings, la división funcionará (porque / convierte), pero si sumas o concatenas sin querer obtendrás resultados inesperados:

javascript
// string — así llega de un input de HTML
const victorias = '78';
// string — así llega de un input de HTML
const partidas  = '120';

// 0.65 — / convierte los strings a número: funciona
victorias / partidas
// '78120' — + concatena strings: bug silencioso
victorias + partidas

La solución: convertir explícitamente con Number() antes de operar:

javascript
// así llega de un input: siempre un string
const partidasTexto = '120';
// convierte '120' (string) a 120 (number)
const partidas = Number(partidasTexto);

// 125 — ya es número: suma de verdad
console.log(partidas + 5);

NaN y Number.isNaN(valor)#

¿Qué pasa si el texto no era un número en absoluto?

javascript
// el usuario escribió letras donde esperábamos un número
const entrada = 'abc';
// NaN — "Not a Number": la conversión no pudo completarse
const num = Number(entrada);
// NaN
console.log('Resultado: ' + num);

NaN es un valor especial que significa “resultado numérico inválido”. Tiene una particularidad importante: no es igual a sí mismo:

javascript
// false — NaN nunca es === a nada, ni a sí mismo
console.log('NaN === NaN: ' + (num === num));

Por eso no puedes detectarlo con ===. La forma correcta es Number.isNaN(valor), que devuelve true solo cuando el valor es exactamente NaN:

javascript
// así llega de un input con letras
const entradaInvalida = 'abc';
// NaN
const convertido = Number(entradaInvalida);

// true — Number.isNaN detecta el NaN sin ambigüedad
console.log('Es NaN: ' + Number.isNaN(convertido));

// Para comparar con el isNaN global (que existe pero convierte antes):
// isNaN('abc')   → true  (lo convierte a número primero: NaN)
// isNaN(null)    → false (lo convierte a 0: no es NaN)
// Number.isNaN(null) → false (correcto: null no es NaN)
// Number.isNaN('abc') → false — ¡OJO! No convierte: 'abc' no ES NaN todavía.
//   Necesitas convertir primero con Number(), luego comprobar.
// false — 'abc' como string no es NaN en sí mismo; Number.isNaN no convierte
console.log('Number.isNaN("abc"): ' + Number.isNaN('abc'));
// true  — convertimos primero a número (NaN), luego comprobamos: correcto
console.log('Number.isNaN(Number("abc")): ' + Number.isNaN(Number('abc')));

La regla: primero Number(valor), luego Number.isNaN() para detectar conversiones fallidas. Es la guardia que usarás en el nivel excelente del ejercicio.

Truthy y falsy#

En JavaScript, cualquier valor tiene un “lado verdadero o falso”: el lenguaje lo evalúa como verdadero (truthy) o falso (falsy) sin necesidad de compararlo con true o false. Para ver de cuál se trata, puedes convertirlo a booleano de forma explícita con Boolean(valor) —igual que antes convertías a número con Number(valor)—: te devuelve true si el valor es truthy y false si es falsy.

Los únicos seis valores falsy son:

javascript
// Valores falsy — los seis únicos:
// el booleano false
false
// el número cero (también -0)
0
// string vacío
''
// ausencia intencional de valor
null
// variable declarada pero sin asignar
undefined
// resultado de operación numérica inválida ('abc' / 2)
NaN

Todo lo demás —cualquier número distinto de cero, cualquier string no vacío, cualquier objeto, cualquier array— es truthy:

javascript
// Valores truthy — ejemplos:
// cualquier número distinto de 0
1
// los negativos también son truthy
-5
// cualquier string no vacío
'Tracer'
// ojo: un espacio en blanco también es truthy
' '
// un array vacío es truthy (tiene referencia en memoria)
[]
// un objeto vacío también es truthy
{}

Compruébalo convirtiendo a booleano con Boolean():

javascript
// string vacío
const nombre = '';

// false — '' es falsy
console.log(Boolean(nombre));
// true  — un string no vacío es truthy
console.log(Boolean('Tracer'));
// false — el cero es falsy
console.log(Boolean(0));
// true  — cualquier número distinto de 0
console.log(Boolean(120));

El operador ! invierte ese lado truthy/falsy: !nombre es true cuando nombre es falsy. Y como '', null y undefined son los tres falsy, una sola comprobación !nombre los detecta de golpe:

javascript
// true  — '' es falsy, ! lo invierte
console.log(!nombre);
// false — un string con texto es truthy
console.log(!'Tracer');

En la sección siguiente lo verás a fondo junto a && y ||.

Operadores lógicos: &&, ||, !#

Con truthy/falsy ya tienes el vocabulario. Ahora los operadores para combinar y negar condiciones o valores:

! — negación#

! invierte el lado verdadero/falso de un valor. Ya lo viste más arriba con !nombre: si nombre es falsy, !nombre es true; si es truthy, !nombre es false.

javascript
// true  — '' es falsy, ! lo invierte
const nombreVacio = '';
console.log('nombre vacío: ' + !nombreVacio);

// false — 'Tracer' es truthy, ! lo invierte
const nombreRelleno = 'Tracer';
console.log('nombre relleno, negado: ' + !nombreRelleno);

&& — Y lógico#

&& devuelve true solo cuando ambos lados son truthy. Si el primero es falsy, devuelve ese primer valor falsy directamente sin evaluar el segundo:

javascript
// ¿El héroe tiene nombre Y partidas positivas?
const nombre = 'Tracer';
// string '120' — tratado como truthy
const partidas = '120';

// true — ambos son truthy
console.log('tiene datos: ' + (Boolean(nombre) && Boolean(partidas)));

// false — nombre vacío es falsy; la comprobación falla
const sinNombre = '';
console.log('sin nombre: ' + (Boolean(sinNombre) && Boolean(partidas)));

|| — O lógico#

|| devuelve true cuando al menos uno de los lados es truthy. Si el primero es truthy, devuelve ese valor directamente sin evaluar el segundo:

javascript
// ¿El héroe tiene rol asignado O está marcado como apoyo de emergencia?
const rolAsignado = '';
// string no vacío — truthy
const apoyo = 'Apoyo de emergencia';

// true — apoyo es truthy, basta con uno
console.log('tiene cobertura: ' + (Boolean(rolAsignado) || Boolean(apoyo)));

// false — los dos son falsy
const sinRol = '';
const sinApoyo = '';
console.log('sin cobertura: ' + (Boolean(sinRol) || Boolean(sinApoyo)));

Dónde se usan de verdad es dentro de una condición para decidir qué código se ejecuta: eso son los condicionales (if), que ves en el próximo capítulo. Los retomas ahí a fondo, incluyendo el cortocircuito. Por ahora, saber qué hacen y reconocerlos es suficiente.

Valor vs referencia (adelanto)#

Los tipos primitivos (number, string, boolean, null, undefined) se almacenan por valor: cuando asignas una variable o la comparas, trabajas con el valor directamente.

Los objetos y los arrays —que verás en profundidad en el capítulo de Arrays y objetos— se almacenan por referencia: la variable no contiene el objeto, contiene un puntero a su posición en memoria. Esto tiene una consecuencia directa en las comparaciones.

Aquí solo necesitas saber leer un objeto: { rol: 'Tanque' } es una agrupación de datos escrita entre llaves; dentro tiene una propiedad llamada rol cuyo valor es 'Tanque'. Por ahora basta con eso para entender el ejemplo:

javascript
// Los primitivos se comparan por VALOR — dos variables con el mismo valor son ===.
const a = 120;
const b = 120;
// true — mismo valor, mismo tipo
a === b

const x = 'Tracer';
const y = 'Tracer';
// true — mismo string
x === y

// Los objetos se comparan por REFERENCIA — aunque tengan el mismo contenido.
// obj1 apunta a una zona de memoria
const obj1 = { rol: 'Tanque' };
// obj2 apunta a OTRA zona distinta
const obj2 = { rol: 'Tanque' };

// false — son dos objetos distintos en memoria,
// aunque tengan exactamente el mismo contenido
obj1 === obj2

=== compara las referencias, no los contenidos. Como obj1 y obj2 apuntan a dos zonas de memoria distintas, el resultado es false. Esto no es un bug: es el comportamiento correcto del lenguaje, y necesitas saber que existe para no sorprenderte cuando te aparezca.

Los primitivos no tienen este problema: dos variables con el mismo número o el mismo string son siempre ===.

Pruébalo tú#

Ejecuta el código y observa la consola. Fíjate especialmente en los casos de coerción con + y -, en los resultados de === frente a ==, y en el comportamiento de los dos objetos con el mismo contenido.

Comprueba lo que sabes#

Pregunta 1 de 5

¿Qué devuelve la expresión 0 == false en JavaScript?

Tu turno#

Los datos de un héroe llegan del formulario como strings. Valida que son utilizables, calcula el winrate y determina si supera el umbral. Cuando lo tengas (o si te atascas), despliega las soluciones y observa el salto de un nivel al siguiente: ahí está lo valioso.

Ejercicio · en esta página

Valida los datos de un héroe

Los datos de un héroe llegan de un formulario como strings. Comprueba que son válidos, calcula el winrate y determina si supera el umbral. Compara también las partidas de dos héroes.

Paso 1: Que funcione

  • La validación detecta nombre vacío y partidas no positivas.
  • El winrate se calcula y se muestra en consola.
  • La comparación de partidas produce el resultado correcto.
Ver soluciones
// ════════════════════════════════════════════════════════════════════════════
// NIVEL OK — "funciona"
//
// Enfoque base: comparaciones con == y confianza en la coerción implícita.
// Operadores como >, < y / convierten los strings a número por su cuenta antes
// de comparar u operar, así que '120' > 0 da true y los cálculos funcionan.
//
// Funciona en este caso concreto. Sus límites (los que arregla el nivel Mejor):
//   - == puede dar resultados sorprendentes: '' == 0 es true, null == undefined
//     es true, así que una validación con == puede dejar pasar datos basura.
//   - Si partidas llega como null o undefined, la operación da NaN y no
//     detectamos el error antes de calcular.
// ════════════════════════════════════════════════════════════════════════════

const nombre = "Tracer";
const partidas = "120";
const victorias = "78";
const umbral = 0.6;

// Validez como un único booleano. != '' descarta el vacío; > 0 y >= 0 fuerzan número.
// La coerción lo hace "funcionar" aquí, pero es frágil (ver cabecera).
const datosValidos = nombre != "" && partidas > 0 && victorias >= 0;
// true
console.log("Datos válidos para " + nombre + ": " + datosValidos);

// La división convierte los strings a número implícitamente.
const winrate = victorias / partidas;
// 0.65
console.log("Winrate de " + nombre + ": " + winrate);

// ¿Supera el umbral? La comparación devuelve directamente un booleano.
// true
console.log(nombre + " cumple el umbral: " + (winrate >= umbral));

// Comparación de partidas con == — aquí ambas son strings, compara texto con texto.
const nombre2 = "Genji";
const partidas2 = "120";
// true
console.log("Mismas partidas: " + (partidas == partidas2));

Por qué este nivel

  • Resuelve el problema con == y operadores (>, /) que convierten los strings a número por su cuenta antes de comparar u operar.
  • Funciona en el caso normal y produce el resultado correcto: es el primer requisito.
  • Su límite: == tiene reglas de coerción no intuitivas. Si partidas fuera null o '', la comprobación partidas > 0 devuelve false (que es correcto aquí por casualidad), pero confiar en eso en código real es una deuda técnica.