Módulo 00 // Unidad 02

Tipos dinámicos y coerción

Por qué una variable puede cambiar de tipo y qué hace JavaScript cuando mezclas tipos

Comprobando la sesión…

Esta unidad es para alumnos del curso. Inicia sesión con tu cuenta de Google para verla.

Esa cuenta no tiene acceso a este curso. Prueba con la cuenta con la que te inscribiste.

No se ha podido cargar el inicio de sesión de Google. Revisa la conexión o si un bloqueador lo impide, y recarga la página.

Vienes de un lenguaje donde cada variable tiene un tipo fijo y el compilador vigila que no lo mezcles. JavaScript funciona al revés: los tipos existen, pero van con los valores, y cuando mezclas tipos el lenguaje no protesta, sino que intenta convertirlos. Conocer esas reglas es la diferencia entre entender un resultado raro y quedarte mirándolo.

1 // Declarar variables: let y const

JavaScript tiene tres palabras para declarar variables, pero en código moderno sólo se usan dos:

const iva = 0.21;        // no se puede reasignar
let total = 100;         // se puede reasignar
total = total * (1 + iva);

var antiguo = 'evítalo'; // la forma de antes de 2015

La regla práctica es sencilla: usa const por defecto y cambia a let sólo cuando necesites reasignar. Así, cuando alguien lee let, sabe que ese valor va a cambiar más adelante. var tiene un comportamiento de ámbito distinto, que verás en la unidad de ámbito y closures, y no aporta nada que let no haga mejor.

const no significa inmutable. Impide reasignar la variable, pero no impide modificar el valor si este es un objeto o un array. Lo verás con calma en la sección 3, cuando conozcas los tipos.

2 // Los tipos de JavaScript

No declaras el tipo, pero cada valor tiene uno. Hay siete tipos primitivos y un tipo objeto, que engloba todo lo demás (arrays, funciones, fechas...):

Tipo Valores typeof
number 42, 3.14, NaN, Infinity 'number'
string 'hola', "hola", `hola ${nombre}` 'string'
boolean true, false 'boolean'
undefined variable sin valor asignado 'undefined'
null «sin valor» puesto a propósito 'object'
bigint 9007199254740993n 'bigint'
symbol identificadores únicos 'symbol'
objeto {}, [], funciones, fechas 'object' o 'function'

Tres diferencias con Java que conviene tener claras desde ya:

  • No hay int, double ni float: todo número es un number de coma flotante de 64 bits. Por eso 0.1 + 0.2 da 0.30000000000000004, igual que un double en Java.
  • Hay dos «vacíos»: undefined es lo que obtienes cuando algo no tiene valor (una variable sin inicializar, una propiedad que no existe); null es un valor que tú asignas para decir «vacío a propósito».
  • typeof null devuelve 'object'. Es un error histórico del lenguaje que nunca se ha corregido para no romper la web existente.

Los textos pueden escribirse con comillas simples, dobles o acentos graves. Estos últimos son template literals y permiten insertar expresiones: `Tienes ${edad} años`. Compara cómo queda el mismo mensaje concatenando y con un template literal:

const nombre = 'Ana';
const edad = 20;

'Hola, ' + nombre + '. Tienes ' + edad + ' años.';  // concatenando con +
`Hola, ${nombre}. Tienes ${edad} años.`;            // 'Hola, Ana. Tienes 20 años.'

// Dentro de ${} vale cualquier expresión, no sólo variables
`El año que viene tendrás ${edad + 1} años`;        // '... tendrás 21 años'
`Eres ${edad >= 18 ? 'mayor' : 'menor'} de edad`;   // 'Eres mayor de edad'

// Y admiten saltos de línea sin caracteres especiales
const aviso = `Pedido confirmado
Llegará en ${2 + 1} días`;

Lo que va entre ${ y } se evalúa y se convierte en texto. Sólo funciona con acentos graves: 'Tienes ${edad} años', con comillas simples, se muestra tal cual, con el ${edad} escrito.

3 // const, valores y referencias

Con los tipos delante ya se puede deshacer una de las confusiones más habituales. const no significa inmutable: significa que la variable no se puede reasignar, es decir, que no puede pasar a guardar otra cosa. Lo que eso implica depende de qué guarda la variable, y ahí entran dos ideas:

  • Valor. Con un primitivo (número, texto, booleano...), la variable guarda el dato mismo. Además, los primitivos son inmutables: ninguna operación cambia el 5 o el texto 'hola'; lo que hacen es producir un valor nuevo.
  • Referencia. Con un objeto o un array, la variable no guarda el objeto, sino una referencia a él: algo parecido a la dirección del sitio de la memoria donde está. El objeto vive aparte, y se puede modificar por dentro.
CONST BLOQUEA LA VARIABLE, NO EL VALOR primitivo: la variable guarda el valor const iva 0.21 iva = 0.10; TypeError: no se puede reasignar iva + 0.10; 0.31 es un valor nuevo; iva sigue igual un primitivo nunca cambia por dentro objeto: la variable guarda una referencia const notas array [7, 9, 5] notas.push(5); permitido: cambia el array [1, 2] notas = [1, 2]; TypeError: no puede apuntar a otro
A la izquierda el valor está dentro de la variable, así que bloquear la variable bloquea el valor. A la derecha lo bloqueado es la flecha: el array al que apunta se puede cambiar, pero la flecha no puede apuntar a otro.

Traducido a código:

const iva = 0.21;
iva = 0.10;                  // TypeError: Assignment to constant variable.

const notas = [7, 9];
notas.push(5);               // válido: notas es ahora [7, 9, 5]
notas[0] = 10;               // válido: notas es ahora [10, 9, 5]
notas = [1, 2];              // TypeError: intentaría apuntar a otro array

const alumno = { nombre: 'Ana' };
alumno.nombre = 'Eva';       // válido: cambia una propiedad del objeto
alumno = { nombre: 'Eva' };  // TypeError: sería otro objeto

La misma diferencia aparece al copiar una variable en otra, aunque no haya ningún const de por medio. Con un primitivo se copia el valor; con un objeto se copia la referencia, así que las dos variables acaban llegando al mismo objeto:

let a = 5;
let b = a;           // b recibe una copia del 5
b = 8;
console.log(a);      // 5: a no se ha enterado

const lista = [1, 2];
const otra = lista;  // otra recibe la misma referencia, no un array nuevo
otra.push(3);
console.log(lista);  // [1, 2, 3]: sólo hay un array
Aspecto Primitivo Objeto o array
Qué guarda la variable El valor Una referencia al objeto
Qué copia b = a El valor: dos datos independientes La referencia: dos nombres, un objeto
Qué impide const Cambiar el valor Apuntar a otro objeto
Se puede modificar por dentro No, nunca Sí, aunque la variable sea const
Qué compara === Los valores Si es el mismo objeto

Si de verdad necesitas que un objeto no cambie, existe Object.freeze(objeto), que impide añadir, borrar o modificar sus propiedades. Sólo congela el primer nivel: si una propiedad es a su vez un array, ese array se sigue pudiendo modificar.

const protege la variable, no el valor. Con un primitivo, en la práctica es lo mismo; con un objeto, el contenido sigue siendo modificable. Cómo copiar objetos de verdad lo verás en la unidad de objetos, arrays y referencias.

4 // Tipado dinámico y coerción

Como el tipo va con el valor, una misma variable puede contener un número y después un texto. Eso es el tipado dinámico. La consecuencia importante no es esa, sino lo que ocurre cuando un operador recibe tipos que no esperaba: JavaScript no lanza un error, sino que convierte uno de los valores. Esa conversión automática se llama coerción.

COERCIÓN · QUÉ HACE JAVASCRIPT CON TIPOS MEZCLADOS expresión regla que se aplica resultado '5' + 3 hay un texto: + concatena '53' '5' - 3 - sólo es numérico: '5' → 5 2 '5' == 5 convierte y luego compara true '5' === 5 tipos distintos: no convierte false la comparación estricta nunca convierte tipos: úsala siempre
Compara las dos primeras filas: el mismo par de valores da resultados de tipos distintos según el operador. Y compara las dos últimas: la triple igualdad es la única que no convierte.

Las reglas que más vas a encontrar:

  • + con algún texto concatena: 'Total: ' + 10 da 'Total: 10'.
  • -, *, / y las comparaciones <, > convierten a número: '10' * 2 da 20.
  • Si la conversión a número no tiene sentido, el resultado es NaN (Not a Number), que además tiene la rareza de no ser igual a sí mismo: para comprobarlo se usa Number.isNaN(valor).

Y la más importante: existen dos igualdades. == convierte los tipos antes de comparar, con reglas difíciles de recordar ('' == 0 es true, null == undefined es true). === compara valor y tipo, sin convertir nada.

Usa siempre === y !==. Cuando necesites convertir, hazlo tú de forma explícita con Number(texto), String(numero) o Boolean(valor), para que quien lea el código vea la conversión.

5 // Truthy y falsy

Una condición (if, while, &&, ||) no necesita un booleano: acepta cualquier valor y lo convierte. Sólo hay un puñado de valores falsy, que se convierten en false. Todo lo demás es truthy, y se convierte en true:

Tipo Falsy Truthy
Booleanos false true
Números 0, -0, NaN -1, 0.5, Infinity
BigInt 0n 1n, -5n
Textos '' (texto vacío) ' ' (un espacio), '0', 'false'
Vacíos null, undefined —
Objetos — [], {}, cualquier función

Fíjate en la columna de la derecha: un texto sólo es falsy si está vacío, da igual lo que ponga dentro, y un objeto nunca es falsy, aunque no tenga nada. Para ver cómo se convierte un valor sin escribir un if, usa Boolean(valor):

Boolean(0);          // false
Boolean('0');        // true: es un texto con un carácter
Boolean('false');    // true: es un texto, no el booleano
Boolean([]);         // true: todo objeto es truthy
Boolean(null);       // false

Donde más se nota es en las condiciones. Estos dos casos aparecen en casi cualquier formulario o listado:

const nombre = campoNombre.value;  // '' si el usuario no escribe nada
if (nombre) {
  console.log(`Hola, ${nombre}`);
} else {
  console.log('Escribe tu nombre');
}

const resultados = [];
if (resultados) {
  console.log('Hay resultados');   // se muestra siempre: [] es truthy
}
if (resultados.length) {
  console.log('Hay resultados');   // sólo si hay elementos: 0 es falsy
}

Esto permite escribir condiciones cortas, pero también esconde errores. El operador ?? (nullish coalescing) ayuda en el caso más frecuente: dar un valor por defecto sólo cuando el valor es null o undefined, sin tratar el 0 o el texto vacío como ausencia.

Una tienda guarda el número de unidades en carrito y quiere mostrar 1 si el dato no existe. El código original es const unidades = guardado || 1;.

  1. Si guardado es undefined: undefined es falsy, así que || devuelve 1. Correcto.
  2. Si guardado es 3: 3 es truthy, || devuelve 3. Correcto.
  3. Si guardado es 0 (el usuario vació el carrito): 0 es falsy, así que || devuelve 1. Incorrecto.
  4. Con guardado ?? 1: ?? sólo sustituye null y undefined, así que el 0 se respeta.
El operador adecuado es ??: const unidades = guardado ?? 1;. || sirve cuando cualquier valor falsy significa «no hay dato»; ??, cuando 0, '' o false son datos válidos.

6 // Para practicar

Tres ejercicios cortos para practicar template literals, conversiones explícitas y operaciones con números que no son enteros. Cada ejercicio trae el enunciado en los comentarios del código.

Saludos con template literalstraining-daw-sv/modulo-00/02-tipos-dinamicos-y-coercion/01_template_stringsCalculadora y coerción de tipostraining-daw-sv/modulo-00/02-tipos-dinamicos-y-coercion/02_calculatorConversor de segundostraining-daw-sv/modulo-00/02-tipos-dinamicos-y-coercion/03_seconds_converter

Autoevaluación

En Java, int edad = 20; edad = "veinte"; no compila. En JavaScript el equivalente funciona. ¿Qué diferencia de diseño hay detrás?
En JavaScript el tipo pertenece al valor, no a la variable. Una variable es sólo un nombre que apunta a un valor, y puede apuntar a valores de cualquier tipo a lo largo del programa. En Java el tipo se fija en la declaración y el compilador lo comprueba; en JavaScript nadie lo comprueba hasta que se ejecuta.
¿Por qué '5' + 3 da '53' pero '5' - 3 da 2?
Porque cada operador decide a qué tipo convertir. El + sirve para sumar y para concatenar, y si uno de los operandos es texto concatena. El - sólo tiene sentido con números, así que convierte '5' en 5 antes de operar.
Si const lista = [] es una constante, ¿cómo es posible que lista.push(1) funcione?
const impide reasignar la variable, no modificar el valor. lista sigue apuntando al mismo array; lo que cambia es el contenido de ese array. Lo que daría error es lista = [1], que intenta apuntar la variable a otro valor.
Un formulario lee una cantidad con input.value y el código hace if (cantidad). Con la cantidad 0 el if sí entra. ¿Por qué?
Porque input.value siempre es un texto, y '0' es truthy. Sólo la cadena vacía es falsy entre los textos. El número 0 sería falsy, pero aquí no hay número hasta que lo conviertas con Number(input.value).