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

Unidad para alumnos del curso

Comprobando tu sesión…

Inicia sesión con la cuenta de Google con la que estás matriculado para ver esta unidad.

Esa cuenta no tiene acceso a este curso. Prueba con otra.

No se ha podido cargar el inicio de sesión de Google. Comprueba la conexión o si algún 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).