====== Otras características de ECMAScript ====== Módulo perteneciente al curso [[informatica:programacion:cursos:programacion_avanzada_javascript|Programación avanzada con JavaScript y ECMAScript]]. ===== Operador de exponenciación ===== ECMAScript 2016 incluyó un nuevo **operador de exponenciación** (''%%**%%'') que permite calcular la **potencia de un número**. Así ''%%3 ** 2%%'' da ''9'' (tres al cuadrado) y ''%%3 ** 3%%'' da como resultado ''27'' (tres al cubo). Es pues, equivalente a usar ''Math.pow''. ===== Inclusión de elementos en una matriz ===== ''Array.prototype.includes'' que permite saber si un elemento está contenido en un array. Así: ** ''%%['E','S','2','0','1','6'].includes('S')%%'' devuelve ''true''. *''%%['E','S','2','0','1','6'].includes('W')%%'' devuelve ''false''. En general podemos afirmar que ''myArray.includes(x)'' devuelve el mismo resultado que ''%%myArray.indexOf(x) !== -1%%''. Existe una excepción a esta norma y tiene que ver con ''NaN'', ese curioso valor que es distinto a todos los demás y a si mismo. Y es que ''[NaN].indexOf(NaN)'' vale ''-1'', pero ''[NaN].includes(NaN)'' devuelve ''true''. ===== Valores por defecto para parámetros opcionales ===== 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 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 (ECMAScript 5) podemos sacar partido de los valores "verdadosos" y "falsosos" (//truly// y //falsy//) generados por conversiones implícitas a booleanos, para así especificar valores por defecto para parámetros de funciones en caso de que falten. Como sabes, 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. El resumen de lo anterior es que, con JavaScript "clásico" es posible definir parámetros opcionales con valores por defecto, pero como no tengamos cuidado podemos meter la pata bien a fondo. ==== Cómo hacerlo bien con ECMAScript ==== Por suerte, desde ECMAScript 2020 existe 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 ([[https://www.jasoft.org/Blog/post/PermaLinkaspxguid=27d08dec-4958-4961-9e5e-c1a1d29.aspx|en C#]], por ejemplo, desde hace 15 años al menos), pero en JavaScript/ECMAScript [[https://github.com/tc39/proposal-nullish-coalescing/|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 ??'': {{ :informatica:programacion:cursos:programacion_avanzada_javascript:22-null-coalescing-error.png |}} 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. ===== El operador de encadenamiento opcional: evitando errores por nulos ===== El operador de encadenamiento opcional, ''?.'' apareció con ECMAScript 2020 y nos permite simplificar enormemente las expresiones con propiedades o arrays que podrían tener valores nulos. Un caso habitual es el siguiente: estamos accediendo a un servicio que nos devuelve información de objetos que necesitamos para nuestra aplicación. Estos objetos pueden tener a su vez varios subobjetos a los que necesitamos acceder para hacer cosas, por ejemplo: if (usuario.direccion.planta.length > 0) { //lo que sea... } El problema de lo anterior es que es posible que el usuario no tenga dirección, y si la tiene quizá no tiene el campo de la planta en la que vive (porque es una casa unifamiliar, por ejemplo). Por lo que la expresión anterior no se puede utilizar ya que **cualquier punto de la cadena de propiedades puede ser nulo y fallar**. Por ello, en estos casos se suele escribir algo como esto cuando usamos JavaScript "clásico" (ECMAScript 5): if (usuario && usuario.direccion && usuario.direccion.planta) { if (usuario.direccion.planta.length > 0) { //lo que sea... } } Es decir, antes de poder utilizar nuestra expresión original debemos comprobar que todos los elementos de la cadena de propiedades existen (no son nulos), para asegurarnos de que no se produce un error. Obviamente sólo es necesario comprobar aquellos que puedan estar nulos. Por ejemplo, su el usuario siempre va a existir como resultado de la llamada al servicio (o sea, el servicio no nos va a devolver nunca un nulo), la primera comprobación (''if (usuario)'') no es necesaria. Lo anterior funciona porque el operador AND (''&&'') tiene cortocircuito de expresiones, es decir, dado que para ser cierto ambas partes de la comparación deben ser ciertas, en cuanto la primera no lo es ya no se comprueba la segunda, por lo que en cuanto cualquier parte de la cadena de propiedades se encuentra un nulo, no se evalúan las demás, y no se produce nunca un error por nulos. Este tipo de expresiones es muy habitual verlas por ahí y funcionan bien, pero **son tediosas de escribir, largas y no contribuyen precisamente a la claridad del código**. Por suerte, ECMAScript nos proporciona un elemento específico para facilitarnos la vida: el operador de encadenamiento opcional: ?.. Gracias a él podemos escribir esto: if (usuario?.direccion?.planta?.length > 0) { //lo que sea... } Es decir, al ponerlo delante de una propiedad, ésta **sólo se evalúa si el objeto precedente no es nulo ni ''undefined''** (o no es "nuloso", o sea, algo que el lenguaje interpreta como nulo). Lo que se obtiene en caso de que algún punto de la cadena sea nulo o no definido es **un valor no definido**, o sea ''undefined''. Esto es equivalente a usar todos aquellos ''&&'' de antes, pero **mucho más claro de leer y sobre todo de escribir**. MUY IMPORTANTE: el primer objeto de la cadena debe existir y estar definido, ya que el operador necesita al menos un objeto "padre" inicial del que partir. Es decir, en nuestro ejemplo, ''usuario'' debe existir, estar definido y no ser nulo. Si se obtiene como resultado de una llamada a un servicio, por ejemplo, debemos recibir algo, aunque sea un objeto vacío, un número o lo que sea, si no recibiremos un error. ==== Llamadas a funciones ==== Pero este operador sirve para más cosas que simplemente encadenar propiedades de objetos. También es posible utilizarlo para hacer **llamadas a funciones, sólo si la función está definida**. Por ejemplo: var res = usuario?.haceralgo?.(); Esta sintaxis tan rara, ''?.()'' lo que quiere decir es que, si está definida la función, llámala, y si no lo está, devuelve un ''undefined''. De este modo podemos crear interfaces que tengan miembros opcionales, que sólo se implementan según ciertas condiciones, o podemos llamar a diferentes versiones de una API sin que rompa todo en caso de que un método no esté implementado. Muy útil. Es importante señalar que si el supuesto método existe pero no es un método, se producirá un error de tipos. Es decir, lo anterior funciona si el miembro que queremos llamar no existe. Pero, en nuestro ejemplo, si ''usuario.hazAlgo'' existe y es otra cosa que no es una función (por ejemplo un array o una simple propiedad) se producirá un ''TypeError''. Esto es una buena política porque sino podrían darse otros errores diferentes. De este modo tenemos que ser conscientes que vamos a llamar a una posible función. ==== Cortocircuito de expresiones ==== Este operador también incluye cortocircuito de expresiones. Es decir, que en cuanto se encuentra un eslabón de la cadena que es nulo o no definido, no sigue evaluando los demás. Así, por ejemplo: var n = 0; var res = usuario?.haceralgo?.(n++); ...si el usuario o si la función ''hacerAlgo'' no existe y por tanto no se ejecuta, la variable n tampoco aumenta de valor ya que se detiene la evaluación de la expresión posterior a ella. ==== Acceso a arrays ==== En JavaScript una alternativa a la sintaxis ''.'' para acceder a una propiedad es mediante indexación por nombre, así: var color = usuario.ajustes['color']; que es equivalente a: var color = usuario.ajustes.color; La ventaja de la primera sintaxis es que nos permite decidir el nombre de la propiedad dinámicamente, es decir, no va escrita en el código sino que que podemos recibirla en una variable: var nomAjuste = obtenerNombreAjusteColor(); //De un servicio por ejemplo, devuelve 'color' var color = usuario.ajustes.[nomAjuste]; cosa que no podríamos hacer con la sintaxis habitual del punto. En este caso, si la propiedad es nula o no está definida JavaScript devuelve un error. O sea, en este ejemplo si el servicio nos devuelve un ''null'' en la propiedad ''ajustes'' o si directamente no la define, se produciría un error. Para facilitar este caso, una tercera sintaxis de este operador nos permite **acceder al contenido de arrays o colecciones aunque sean nulas o no estén definidas**: var color = usuario?.ajustes?.['color'].valor; En este caso está intentando acceder a las propiedades del objeto ''ajustes'' por su nombre. Fíjate además que, por el cortocircuito de expresiones, si ''ajustes'' es nulo o no está definido se devuelve un ''undefined'' y ya no se intenta leer la propiedad ''valor'' del resultado tampoco (de todos modos si creemos que ''ajustes'' puede existir pero el ajuste en concreto no, convendría usar ''?.'' también con esta última propiedad, claro). Nos vale también para arrays normales y corrientes accedidas mediante índice: var color = usuario?.ajustes?.[0]; De este modo si ''ajustes'' no está definido o es nulo no se producirá un error, como sí ocurriría sin este operador. ==== Devolver algo diferente a no definido ==== Finalmente, podemos combinar ese nuevo operador con el operador de unión "nulosa", que ya conocemos, para devolver un valor por defecto en estos casos, así: var color = usuario?.ajustes?.['color'].valor ?? "#fff"; que en caso de no estar definido devolvería un color por defecto (el color blanco). ==== En resumen ==== El operador ''?.'' es muy útil para escribir expresiones concisas y legibles sin arriesgarnos a que un valor nulo o no definido provoque un error en nuestro código. Su uso es extremadamente sencillo y sólo hay que conocer los pequeños detalles asociados que se explican en esta lección. El soporte de este operador es total en los navegadores //evergreen//, o sea, en todos los actuales (Chrome, Firefox, Opera, Safari, Brave...), así que podemos usarlo sin miedo salvo que tengamos que dar soporte a versiones antiguas o a Internet Explorer por algún motivo.