Menú de navegaciónMenú
Categorías

La mejor forma de Aprender Programación online y en español www.campusmvp.es

?id=58071901-7632-4e74-8780-3260d92746b7

El operador de "unión nulosa" o "nullish coalescing" de ECMAScript: parámetros opcionales y valores por defecto en funciones

Imagen ornamental de la portada

Una necesidad muy habitual a la hora de programar es la de obtener valores por defecto para los parámetros de las funciones.

Al contrario que en otros lenguajes como C# o Java, en JavaScript no hay manera de forzar la definición de una función y sus parámetros. Podemos definir una función con tantos parámetros como deseemos, pero eso no significa que luego los otros programadores nos los vayan a pasar siempre.

Por ejemplo, en C# defines una función tan simple como esta:

int Sumar(int a, int b)
{
    return a+b;
}

Que suma dos enteros y siempre tienes que pasarle dos enteros para que funcione. Es decir, intentar una llamada a Sumar(5) daría un error inmediato, sin posibilidad ni de compilar.

Esto parece una obviedad, pero en JavaScript eso no es así en absoluto. Si defino la misma función en este lenguaje:

function sumar(a, b) {
  return a+b;
}

Podría llamarla de la manera obvia: sumar(5,4), pero también podría hacer: sumar(5) o incluso sumar() sin pasarle parámetro alguno. Y funcionaría en todos los casos, sólo que si no le paso algún valor obtendría un NaN (un resultado que no es un número).

Y es que en JavaScript todos los parámetros de una función son opcionales.

En ECMASCript 2015 se añadió la posibilidad de prever esta posibilidad especificando el valor que queremos otorgarle a un parámetro opcional cuando no se pasa. Por ejemplo, la función anterior podríamos definirla así:

function Sumar(a = 0, b = 0)
{
    return a+b;
}

Y ahora sí, podría llamarla como quisiera: pasándole dos, uno o ningún parámetro. En caso de que falte alguno, su valor sería el especificado por defecto, en este caso un 0. También podrían pasarnos más de 2 parámetros, los cuales al no ser tenidos en cuenta, simplemente no tendrían efecto alguno.

De hecho, esta funcionalidad de parámetros por defecto es mas flexible en ECMAScript que en otros lenguajes, como C#, ya que nos permite usar valores de parámetros anteriores para los parámetros opcionales:

function Sumar(a = 0, b = a) {
    return a+b;
}

En este caso, por ejemplo, si no nos pasan el primer argumento le ponemos un 0, pero si no nos pasa en el segundo, le ponemos como valor por defecto ¡lo que valga el primer parámetro!. Incluso podríamos hacer operaciones con ellos o aplicarles funciones, lo que está muy bien.

El problema de estos parámetros opcionales es que sólo actúan si no se le pasa nada, es decir, que si le pasamos algo que no sea un undefined, hacen caso omiso. Esto da lugar a situaciones como esta:

function Sumar(a = 1, b = 1) {
    return a+b;
}

console.log(Sumar(null, null)); //Muestra un 0, no un 2

En este caso llamamos a la función pasándole dos nulos, y en vez de recibir un 2 como resultado, ya que el valor por defecto para los parámetros en caso de no existir es 1, recibimos un 0. El motivo es que, en realidad, los parámetros no faltan en este caso, sino que se recibe un valor, y por lo tanto no se aplica lo de que "falten", así que el valor por defecto no aplica.

La única excepción a esto es que le pasemos un valor undefined, como por ejemplo en este caso:

var v1, v2;

function Sumar(a = 1, b = 1) {
    return a+b;
}

console.log(Sumar(v1, v2)); //Ahora sí muestra un 2

En este caso las variables que pasamos a la función están declaradas pero no definidas por lo que, aunque se las pasemos al a función, es como si no le hubiésemos pasado nada.

La funcionalidad, lo que hace en realidad para considerar si debe aplicar el valor por defecto, es verificar si los parámetros no están definidos.

Por ello, aunque muy útil, no siempre nos va a servir. Muchas veces lo que necesitamos es ofrecer un valor por defecto si el parámetro no está (o sea, es undefined) , pero también si el parámetro es nulo.

Simulando valores por defecto para parámetros opcionales en JavaScript "clásico"

En JavaScript podemos sacar partido de los valores "verdadosos" y "falsosos" (truly y falsy) generados por conversiones implícitas a booleanos (ver el enlace para detalles), para así especificar valores por defecto para parámetros de funciones en caso de que falten.

Como seguramente sabrás, el operador lógico OR (||) se evalúa con cortocircuito de expresiones lógicas de la siguiente manera:

  • Si el primer operando de la comparación es true devuelve ese mismo primer operando.
  • Si el primer operando es false, entonces se devuelve automáticamente el segundo operando.

Sacando partido a esto es muy fácil definir valores por defecto para los parámetros sin tener que escribir condicionales ni código largo que "embarre" la definición de nuestra función.

Por ejemplo, si tenemos una función sumar que toma dos parámetros, a y b, y queremos asegurarnos de que ambos tienen sendos valores por defecto, aunque no se hayan especificado, podemos definirla así:

function sumar(a, b) {
  a = a || 0;
  b = b || 0;
  return a+b;
}

Por el efecto que acabo de explicar, lo que se consigue con la primera línea del cuerpo de la función, es que si no se le pasa a la misma el parámetro a (es decir, se recibe un undefined) se le asignará automáticamente un 0 como valor por defecto. Y lo mismo con b. Al evaluarse el operador OR (||) el primer parámetro se convierte en un booleano. Si no está definido esto es equivalente a un "falsoso" (o sea, se convierte implícitamente en un false) y por lo tanto se asigna en la propia variable el segundo operando del OR (el valor por defecto).

Si se le pasa una cadena o un objeto de cualquier tipo se evaluará como true y por lo tanto se devolverá el propio objeto (se reasignará a sí mismo).

Pero esto tiene algunos fallos. Por ejemplo, si el parámetro que esperábamos es un booleano y queremos que el valor por defecto sea true, si usamos esta técnica, cuando se le pasase un false como valor para el parámetro, el efecto que obtendríamos es que se cambiaría su valor a true y, en la práctica, no lograríamos nunca pasarle un false como valor efectivo. Y si esperásemos una cadena de texto y nos valiesen también cadenas de texto vacías, al hacer algo como esto:

function test(p) {
  p = p || 'Hola';
  return p;
}
console.log(test(''));

veríamos por la consola la cadena 'Hola', y no una cadena vacía, como quizá podríamos haber pensado. El motivo es que las cadenas vacías al forzar su conversión a booleano (con el ||) se interpretan como "falsosas", o sea, con false, por lo que nunca podríamos recibir de este modo una cadena vacía. Lo mismo ocurre, por ejemplo, con el número 0 y otros valores "falsosos".

Existen, por supuesto, formas de lograr lo mismo en estas situaciones, pero añaden complejidad a algo que debería ser más sencillo.

Cómo hacerlo bien con ECMAScript

El resumen de lo anterior es que, con JavaScript "clásico" (ECMAScript 5) es posible definir parámetros opcionales con valores por defecto, pero como no tengamos cuidado podemos meter la pata bien a fondo.

Por suerte, en ECMAScript hay una manera mucho mejor de lograrlo: el operador de unión nulosa, más conocido por su extraño nombre en inglés operador nullish coalescing.

Este operador se representa por una doble interrogación ?? y sirve precisamente para lograr de manera sencilla y directa lo que acabamos de describir: ofrecer un valor por defecto cuando un elemento al que necesitamos acceder no existe, es nulo o está sin definir (undefined).

Este operador lleva existiendo en otros lenguajes desde hace mucho tiempo (en C#, por ejemplo, desde hace 15 años al menos), pero en JavaScript/ECMAScript es una propuesta que hace poco tiempo ha pasado a fase 4 y se ha adoptado por todos los navegadores modernos "evergreen".

Su uso es muy sencillo: se pone a continuación de un valor que creamos que puede ser nulo o no definido y, en caso de que lo sea, devolverá lo que pongamos a su derecha, es decir, esto: var resultado = valor ?? valorXDefecto.

Con él, nuestra función de suma con parámetros opcionales quedaría así:

function sumar(a, b) {
  a = a ?? 0;
  b = b ?? 0;
  return a+b;
}

En este caso siempre va a funcionar bien, no como ocurría con la técnica convencional, que debemos vigilar mucho más de cerca.

Lo importante a considerar aquí es que el valor a la izquierda del operador ?? se comprueba para ver si es null o undefined, o lo que es lo mismo: "nuloso" (nullish). Así que actúa como el operador || pero comprobando si es "nuloso", y no "falsoso", por lo que estas expresiones que con || son problemáticas:

var res = false ?? true; // --> false
res = '' ?? 'Hola'; // --> ''
res = 0 ?? 1; // --> 0

no lo son para el operador de nullish coalescing.

Paréntesis obligatorios

¿Qué ocurre si combinas este operador ?? con otros operadores de tipo lógico? Por ejemplo, ¿cuál sería el resultado de esto?:

var a = 0;
var b = false;
var res = a || b ?? true;

El resultado es que se produce un error: Unexpected token ??:

El error que se produce

El motivo es que, al combinar este tipo de operadores, hagas lo que hagas con la precedencia no puedes evitar que la interpretación que hace el programador de lo que está escribiendo entre en conflicto con lo que el diseñador del lenguaje haya decidido. Por ejemplo, en el fragmento anterior, eso se puede interpretar como:

  • (a || b) ?? true
  • a || (b ?? true)

y en ambos casos tiene sentido.

Así que los diseñadores de esta característica decidieron que no puede utilizarse con || o && salvo que especifiquemos claramente la precedencia mediante paréntesis. De este modo se fuerza a que el programa tome la decisión sobre cómo interpretarlo y además facilita su lectura inequívoca por parte de cualquiera.

Cortocircuito de expresiones

Un último detalle sobre el operador: al igual que sus "hermanos" lógicos, implementa cortocircuito de expresiones. Esto quiere decir que si el elemento a la izquierda no es "nullish" y por lo tanto no se va a devolver el valor por defecto, nunca se llega a evaluar el elemento de la derecha:

var res = "algo" ?? funcionValorPorDefecto();

En este ejemplo, como el operando de la izquierda no es null ni undefined (es una cadena) y por lo tanto no se va a devolver el valor por defecto de la derecha (tras el ??), la llamada a la función no se producirá porque no es necesaria. Así que si cuentas con que se debe llamar siempre a esta función (por ejemplo para inicializar algo), tendrías un problema.. Tenlo en cuenta.

Además esto tiene una aplicación interesante: poder generar una excepción en caso de que falte un parámetro indispensable y al que no se le pueda dar un valor predeterminado:

function transformaObjeto(obj) {
	obj = obj ?? function() {throw 'Debes pasar un objeto!!'}();
  //Resto del código
  console.log(obj);
}

En este caso, si no se le pasa un objeto a la función se produce una excepción que podemos capturar. En caso de pasar un objeto se ejecutaría el código de la función.

Esta función anónima, obviamente, podría ser una función cualquiera a la que se llamaría, y de hecho podría también devolver un valor, el cual se asociaría a la variable obj para asignarle un valor predeterminado siguiendo reglas tan complejas como necesitásemos, encapsuladas en esa función a la que llamamos tras el ??.

 

En resumen

El operador doble interrogación, conocido como de nullish coalescing o de "unión nulosa", es muy simple, pero al mismo tiempo un gran añadido al arsenal de herramientas del programador JavaScript. En especial, a la hora de lidiar con argumentos de funciones, ya que en JavaScript éstos siempre son opcionales, pero sin poder especificar un valor por defecto.

Aunque oficialmente forma parte de ECMAScript 2020, aparecido este mismo mes de junio, está soportado hace ya meses por la práctica totalidad de los navegadores modernos excepto Internet Explorer y en concreto lo soportan:

  • Chrome 80+ y todos los que usan Chromium por debajo (Opera, Microsoft Edge, Brave, Vivaldi...)
  • Firefox 72+
  • Safari 13.1+ para macOS
  • Safari 13.5+ para iOS
  • Chrome 84+ para Android
  • WebView de Android 81+

Así que puedes usarlo con bastante seguridad de que no tendrás problemas, salvo en entornos corporativos o donde no te puedas permitir el lujo de prescindir de usuarios de IE o de otros navegadores de móvil como el de Samsung, el de Xiaomi y similares (aunque probablemente lo incorporen pronto).

¡Espero que te resulte útil!

José Manuel Alarcón Fundador de campusMVP, es ingeniero industrial y especialista en consultoría de empresa. Ha escrito diversos libros, habiendo publicado hasta la fecha cientos de artículos sobre informática e ingeniería en publicaciones especializadas. Microsoft lo ha reconocido como MVP (Most Valuable Professional) en desarrollo web desde el año 2004 hasta la actualidad. Puedes seguirlo en Twitter en @jm_alarcon o leer sus blog técnico o personal. Ver todos los posts de José Manuel Alarcón

¿Te ha gustado este post?
Pues espera a ver nuestro boletín mensual...

Suscríbete a la newsletter

La mejor formación online para desarrolladores como tú

Agregar comentario

Los datos anteriores se utilizarán exclusivamente para permitirte hacer el comentario y, si lo seleccionas, notificarte de nuevos comentarios en este artículo, pero no se procesarán ni se utilizarán para ningún otro propósito. Lee nuestra política de privacidad.