Herramientas de usuario

Herramientas del sitio


informatica:programacion:cursos:programacion_avanzada_javascript:otras_caracteristicas_ecmascript

Diferencias

Muestra las diferencias entre dos versiones de la página.

Enlace a la vista de comparación

Ambos lados, revisión anteriorRevisión previa
Próxima revisión
Revisión previa
informatica:programacion:cursos:programacion_avanzada_javascript:otras_caracteristicas_ecmascript [2024/10/29 16:07] – [Inclusión de elementos en una matriz] tempwininformatica:programacion:cursos:programacion_avanzada_javascript:otras_caracteristicas_ecmascript [2024/10/30 13:13] (actual) – [Paréntesis obligatorios] tempwin
Línea 100: Línea 100:
 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. 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"+==== 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.+En JavaScript (ECMAScript 5) podemos sacar partido de los valores "verdadosos" y "falsosos" (//truly// //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:+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 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.+  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. 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í:+Por ejemplo, si tenemos una función ''sumar'' que toma dos parámetros, ''a'' ''b'', y queremos asegurarnos de que ambos tienen sendos valores por defecto, aunque no se hayan especificado, podemos definirla así:
  
 +<code javascript>
 function sumar(a, b) { function sumar(a, b) {
   a = a || 0;   a = a || 0;
Línea 118: Línea 119:
   return a+b;   return a+b;
 } }
 +</code>
  
-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).+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).+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:+**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:
  
 +<code javascript>
 function test(p) { function test(p) {
   p = p || 'Hola';   p = p || 'Hola';
Línea 130: Línea 133:
 } }
 console.log(test('')); console.log(test(''));
 +</code>
  
-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".+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. 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.+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+==== 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.+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 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".+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.+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í: Con él, nuestra función de suma con parámetros opcionales quedaría así:
  
 +<code javascript>
 function sumar(a, b) { function sumar(a, b) {
   a = a ?? 0;   a = a ?? 0;
Línea 154: Línea 159:
   return a+b;   return a+b;
 } }
 +</code>
  
 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. 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:+Lo importante a considerar aquí es que el valor a la izquierda del operador ''??'' se comprueba para ver si es ''null'' ''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:
  
 +<code javascript>
 var res = false ?? true; // --> false var res = false ?? true; // --> false
 res = '' ?? 'Hola'; // --> '' res = '' ?? 'Hola'; // --> ''
 res = 0 ?? 1; // --> 0 res = 0 ?? 1; // --> 0
 +</code>
  
-no lo son para el operador de nullish coalescing. +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?:+==== 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?:
 +
 +<code javascript>
 var a = 0; var a = 0;
 var b = false; var b = false;
 var res = a || b ?? true; var res = a || b ?? true;
 +</code>
  
-El resultado es que se produce un error: Unexpected token ??:+El resultado es que **se produce un error**''Unexpected token ??'':
  
-El error que se produce+{{ :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: 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'' 
-    a || (b ?? true)+  * ''a || (b ?? true)''
  
 y en ambos casos tiene sentido. 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. +Así que los diseñadores de esta característica decidieron que **no puede utilizarse con ''||'' ''&&'' 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:+==== 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:
 +
 +<code javascript>
 var res = "algo" ?? funcionValorPorDefecto(); var res = "algo" ?? funcionValorPorDefecto();
 +</code>
 +
 +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:
 +
 +<code javascript>
 +if (usuario.direccion.planta.length > 0) {
 + //lo que sea...
 +}
 +</code>
 +
 +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):
 +
 +<code javascript>
 +if (usuario && usuario.direccion && usuario.direccion.planta) {
 +    if (usuario.direccion.planta.length > 0) {
 + //lo que sea...
 + }
 +}
 +</code>
 +
 +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.
 +
 +<WRAP center round tip 60%>
 +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.
 +</WRAP>
 +
 +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:
 +
 +<code javascript>
 +if (usuario?.direccion?.planta?.length > 0) {
 + //lo que sea...
 +}
 +</code>
 +
 +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**.
 +
 +<WRAP center round important 60%>
 +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.
 +</WRAP>
 +
 +==== 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:
 +
 +<code javascript>
 +var res = usuario?.haceralgo?.();
 +</code>
 +
 +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:
 +
 +<code javascript>
 +var n = 0;
 +var res = usuario?.haceralgo?.(n++);
 +</code>
 +
 +...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í:
 +
 +<code javascript>
 +var color = usuario.ajustes['color'];
 +</code>
 +
 +que es equivalente a:
 +
 +<code javascript>
 +var color = usuario.ajustes.color;
 +</code>
 +
 +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:
 +
 +<code javascript>
 +var nomAjuste = obtenerNombreAjusteColor(); //De un servicio por ejemplo, devuelve 'color'
 +var color = usuario.ajustes.[nomAjuste];
 +</code>
 +
 +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**:
 +
 +<code javascript>
 +var color = usuario?.ajustes?.['color'].valor;
 +</code>
 +
 +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:
 +
 +<code javascript>
 +var color = usuario?.ajustes?.[0];
 +</code>
 +
 +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í:
 +
 +<code javascript>
 +var color = usuario?.ajustes?.['color'].valor ?? "#fff";
 +</code>
 +
 +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.
  
-En este ejemplocomo 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 esta función (por ejemplo para inicializar algo), tendrías un problema.. Tenlo en cuenta.+El soporte de este operador es total en los navegadores //evergreen//, o seaen todos los actuales (Chrome, Firefox, Opera, Safari, Brave...), así que podemos usarlo sin miedo salvo que tengamos que dar soporte versiones antiguas o a Internet Explorer por algún motivo.
informatica/programacion/cursos/programacion_avanzada_javascript/otras_caracteristicas_ecmascript.1730214436.txt.gz · Última modificación: por tempwin