Aprende leyendo en orden

null y undefined — leer con seguridad con ?. y ??

Aclara la diferencia entre null y undefined, y aprende a leer datos anidados y a poner valores por defecto sin errores.

Al mostrar datos de un pedido desde un servidor, te toparás con una dirección que nunca se rellenó, o un método de pago aún sin elegir. JavaScript tiene dos valores para este estado de "no hay nada aquí" — undefined y null — y una lectura fallida detiene el procesamiento con un TypeError.

Este artículo cubre la diferencia entre los dos, leer a través de un paso que falta sin detenerse, y aplicar un valor por defecto cuando falta algo.

Los 2 valores que significan que no hay nada — undefined y null

undefined (un valor que JavaScript rellena automáticamente cuando aún no se ha asignado nada) aparece por sí solo — no necesitas asignarlo tú mismo. Ya lo has visto devuelto al leer más allá del final de un array o una clave de objeto no registrada, y una variable solo declarada también cae en esta categoría.

  • Una variable solo declarada, nunca asignada, como let couponCode;
  • Una clave no registrada en un objeto, como order.customer (visto antes)
  • Un index fuera de rango en un array de 2 elementos, como tags[5] (visto antes)

null (un valor que quien escribe asigna deliberadamente para señalar que está vacío), en cambio, nunca aparece por sí solo. Cuando quieres que la ausencia de un valor sea en sí misma un dato — "aún no se ha elegido dirección de envío", "sin cupón aplicado" — le asignas null tú mismo.

El JSON que vuelve de una API también puede llevar null para señalar que un campo está vacío.

Como son valores distintos, === devuelve false al compararlos. ==, en cambio, trata los dos como iguales y devuelve true.

Al escribir una comprobación, o bien sé deliberado sobre cuál de los dos esperas y comprueba solo ese lado, como en === null, o déjaselo por completo a ?. y ??, que se cubren a partir de aquí.

Quién pone undefined y null ahí
Leer una varsolo declaradaJavaScript larellenaundefinedLeer una claveo index faltanteJavaScript larellenaundefinedQuieres señalarque está vacíoQuien escribelo asignanullRellenado automáticamenteAsignado por quien escribe
En las 2 filas superiores, undefined es un valor que JavaScript rellena automáticamente; en la fila inferior, null es un valor que asigna quien escribe.
let couponCode;                 // solo declarada, sin valor asignado
console.log(couponCode);        // undefined

const order = { orderId: "ORD-1105", total: 12800 };
console.log(order.customer);    // undefined (una clave no registrada)

const tags = ["Limited", "Preorder"];
console.log(tags[5]);           // undefined (un index fuera de rango)

// null lo asigna quien escribe
let shippingAddress = null;     // aún no se ha elegido dirección de envío
console.log(shippingAddress);   // null

console.log(shippingAddress === undefined);  // false (son valores distintos)
console.log(shippingAddress == undefined);   // true (== los trata como iguales)

No detenerse cuando falta un paso anidado — optional chaining ?.

Los datos que vuelven de una API no siempre tienen todos los campos rellenados. Código que lee profundo, como order.customer.address.city, se detiene en cuanto siquiera un pedido tiene un address faltante a mitad de camino.

Aquí es cuando quieres que la lectura en sí no falle — que siga adelante como "nada ahí" en su lugar.

Cuando order.customer.address es undefined, intentar leer .city más allá lanza TypeError: Cannot read properties of undefined (reading 'city'). El 1.er nivel de lectura pasa bien, pero se detiene en el 2.º.

Para eso está el optional chaining (el operador ?., que devuelve undefined sin seguir leyendo si el lado izquierdo es null o undefined). Escribe address?.city, y .city solo se lee cuando address tiene un valor — si no, la lectura se detiene ahí y devuelve undefined.

Al pasar una clave por [], escríbelo como ?.[field].

Dónde deja de leer ?.
customertiene un valor?.addresstiene un valor?.cityAustinshippingnull?.carrierse detiene aquíundefinedpaymentsin esa clave?.methodse detiene aquíundefined
Donde hay un valor, sigue adelante. En cuanto encuentra null, undefined, o una clave faltante en sí, se detiene y devuelve undefined sin leer el resto.
const order = {
  orderId: "ORD-1105",
  customer: { name: "Ryan Cole" },
};

// address falta, así que leer city más allá se detendría
// console.log(order.customer.address.city);
// TypeError: Cannot read properties of undefined (reading 'city')

// Inserta ?. y la lectura se detiene, devolviendo undefined
console.log(order.customer.address?.city);   // undefined

// Donde hay un valor, sigue adelante con normalidad
console.log(order.customer.name);            // Ryan Cole

// Pasa una clave por una variable con ?.[]
const field = "city";
console.log(order.customer.address?.[field]);   // undefined

Úsalo solo donde un campo pueda faltar legítimamente

Pon ?. en cada paso, e incluso un nombre de clave mal escrito se cuela como undefined sin dar error. Sáltatelo en campos que seguro estarán ahí, y úsalo solo justo antes de un campo que pueda faltar legítimamente — así los errores que realmente quieres atrapar siguen deteniendo la ejecución.

Lee valores de un objeto con la forma de una respuesta de API de pedido, sin detenerte cuando faltan campos. response ya está declarado, y customer siempre está presente.

① Muestra la ciudad de envío, escrita de forma que no se detenga aunque falte algo en el camino.

② Muestra el nombre del transportista, shipping.carrier, con la misma sintaxis.

③ Muestra el método de pago, payment.method, con la misma sintaxis.

④ Lee un nombre de campo desde una variable, y muestra el código postal de envío.

(Si se ejecuta correctamente, aparecerá una explicación.)

Editor JavaScript / TypeScript

Ejecutar el código para ver el resultado

Fijar un valor por defecto — ?? vs ||

En vez de mostrar un undefined desnudo cuando falta un campo, normalmente querrás mostrar un valor por defecto — "Guest", "20 items".

Usa || para esto, sin embargo, y a veces un valor que sí está ahí acaba reemplazado por el valor por defecto de todas formas. Un contador de stock de 0, o un campo de notas vacío, son ejemplos.

null y undefined juntos se llaman nullish. Usa el operador de fusión nula (el operador ??, que devuelve el lado derecho solo cuando el lado izquierdo es nullish), y 0, una cadena vacía, o false se quedan exactamente como están.

|| devuelve el lado derecho siempre que el izquierdo es falsy, así que no puede distinguir "no hay valor" de "el valor es 0". Cuando tu objetivo es aplicar un valor por defecto, ?? evita el reemplazo no deseado.

?? y || divergen en valores falsy
stock es 0|| lo revisareemplaza falsy20reemplazadostock es 0?? lo revisasolo reemplaza null0sin cambiosstock es null?? lo revisasolo reemplaza null20reemplazado
Los resultados solo divergen cuando stock es 0. ?? solo reemplaza null y undefined, así que 0 se queda como el valor.
Valor del lado izquierdoQué devuelve ||Qué devuelve ??
nullel valor por defectoel valor por defecto
undefinedel valor por defectoel valor por defecto
0el valor por defectose queda en 0
cadena vacíael valor por defectose queda como cadena vacía
falseel valor por defectose queda en false
const settings = { displayName: null, itemsPerPage: 0 };

// ?? devuelve el lado derecho solo cuando el izquierdo es null o undefined
console.log(settings.displayName ?? "Guest");   // Guest
console.log(settings.itemsPerPage ?? 20);        // 0 (se queda tal cual)

// || devuelve el lado derecho siempre que el izquierdo es falsy, así que hasta el 0 se reemplaza
console.log(settings.itemsPerPage || 20);        // 20

// Una clave no registrada recurre al valor por defecto en ambos casos
console.log(settings.language ?? "en");          // en
console.log(settings.language || "en");          // en

Ten en cuenta que escribir ?? y || uno al lado del otro sin paréntesis, como en displayName ?? "Guest" || "Anonymous", es un error de sintaxis — JavaScript no puede decidir cuál evaluar primero. Para usar ambos, sepáralos con paréntesis, como en (displayName ?? "Guest") || "Anonymous".

Aplica un valor por defecto a cada campo de un objeto de configuración de usuario, construyendo los valores que vas a mostrar. Los campos que ya tienen un valor deben quedarse sin tocar. userSettings ya está declarado.

① Aplica un valor por defecto de "Guest" al nombre para mostrar y muéstralo.

② Aplica un valor por defecto de "en" al idioma de visualización y muéstralo.

③ Aplica un valor por defecto de 20 al contador de elementos por página y muéstralo.

④ Aplica un valor por defecto de false al ajuste de notificaciones no registrado y muéstralo.

Editor JavaScript / TypeScript

Ejecutar el código para ver el resultado

No dejes que un valor por defecto borre 0 o una cadena vacía

Un contador de stock de 0, un descuento de 0, una nota vacía — todos ellos cuentan como "hay un valor". Aplica un valor por defecto con ||, y como son falsy, se reemplazan por el defecto, creando un desajuste como un artículo agotado que se muestra como disponible. Usa ?? cuando lo único que te importa es si hay un valor en absoluto.

Compara cómo cambia la visualización según qué operador de valor por defecto uses, para un producto con 0 stock y una nota vacía. product ya está declarado.

① Aplica un valor por defecto de 10 al stock con || y muéstralo.

② Aplica el mismo valor por defecto de 10 al stock con ?? y muéstralo.

③ Aplica un valor por defecto de "No notes" a la nota con ||, envuélvelo entre corchetes, y muéstralo.

④ Aplica el mismo valor por defecto de "No notes" a la nota con ??, envuélvelo entre corchetes, y muéstralo.

Editor JavaScript / TypeScript

Ejecutar el código para ver el resultado

No llamar a algo que no está ahí — la llamada ?.()

En el procesamiento de pedidos, a veces guardarás "qué hacer si tiene éxito" y "qué hacer si falla" dentro de un objeto, llamándolos cuando haga falta. Pero llama a un campo que no tiene nada configurado, y se detiene con TypeError: handlers.onError is not a function — así que necesitas una forma de llamarlo solo cuando realmente está ahí.

La función cubierta en el artículo 3 también puede vivir dentro de un objeto como el valor de una propiedad. El () => ... en el siguiente ejemplo es sintaxis para crear una función al vuelo — esa sintaxis en sí se cubre en la categoría de sintaxis, así que aquí céntrate solo en cómo cambia el resultado según si hay una función en la propiedad.

Llama a una propiedad que podría contener una función como handlers.onError?.(), y cuando onError es null o undefined, la llamada se salta y en su lugar vuelve undefined.

?.() se salta la llamada cuando no hay nada registrado
onSuccess esuna funciónLlama con?.()Se ejecutaonError esnullLlama con?.()Se salta,undefinedonError esnullLlamadasin ?.Se detiene conTypeError
La llama si hay una función ahí; devuelve undefined sin llamar nada si es null. Llamarla sin ?. se detiene con un TypeError.
const handlers = {
  onSuccess: () => console.log("Order confirmed"),
  onError: null,
};

// Hay una función ahí, así que se llama
handlers.onSuccess?.();              // Order confirmed

// Es null, así que la llamada se salta y vuelve undefined
console.log(handlers.onError?.());   // undefined

// Llamar sin ?. detiene la ejecución
// handlers.onError();
// TypeError: handlers.onError is not a function

Compara llamar con ?. frente a llamar sin él, usando un objeto que agrupa manejadores del procesamiento de pedidos. handlers ya está declarado.

① Llama al manejador de éxito usando sintaxis que se salte la llamada si no está registrado.

② Llama al manejador de fallo de la misma forma, y muestra lo que devuelva.

③ Llama a ese mismo manejador de fallo sin ?., y observa qué mensaje detiene la ejecución.

Editor JavaScript / TypeScript

Ejecutar el código para ver el resultado
QUIZ

Verificación de conocimientos

Responde cada pregunta una a una.

Pregunta 1Escribe let couponCode; y léela sin asignar un valor. ¿Cuál es el valor de la variable?

Pregunta 2Cuando el lado izquierdo de ?. es null o undefined, ¿qué le pasa al resto de la cadena?

Pregunta 3Con const discountRate = 0;, ¿qué devuelven discountRate || 5 y discountRate ?? 5?