Booleanos, pseudo-booleanos, operadores y condicionales en Java Script mucho más complejo de lo que parece

Post original en JASoft.org: http://www.jasoft.org/Blog/post/Booleanos-pseudo-booleanos-operadores-y-condicionales-en-JavaScript-mucho-mas-complejo-de-lo-que-parece.aspx

True-or-FalseLo que voy a explicar hoy tiene mucho más trasfondo del que puede parecer a simple vista, y es aplicable no solo a JavaScript sino a cualquier lenguaje débilmente tipado que maneje valores que se pueden evaluar automáticamente como si fueran booleanos.

En JavaScript existen valores booleanos (true y false) pero existen también otros valores que usados en el contexto de un operador booleano se comportan también como si fueran booleanos. A los tipos de datos no booleanos que se interpretan como verdadero cuando se evalúan se les denomina valores “truly” (o como les llamo yo en español: valores “verdadosos”). Cuando se evalúan como falsos se les denomina valores “falsy” (o valores “falsosos”).

Condicionales y conversiones implícitas

Por ejemplo consideremos este código:

 

var v = "cualquier cosa";

if (v)

    alert("verdadero");

else

    alert("falso");

¿Cuál crees que será el resultado que muestra el mensaje?

En este caso el resultado será ver por pantalla “verdadero”. El motivo es que dentro de la expresión a evaluar en el condicional tenemos una cadena de texto y ésta se convierte automáticamente a un booleano para poder devolver un resultado y ejecutar la rama pertinente del condicional. Como es un valor “verdadoso”, el resultado de convertirlo es true.

El apartado 12.5 de la especificación ECMAScript indica que se debe llamar al método interno ToBoolean() sobre el resultado de evaluar la expresión que hay dentro de los paréntesis del if. Por ello se convierte automáticamente la cadena en un booleano.

Reglas de conversión a booleano

Las reglas de conversión se indican en la especificación ECMAScript, y dependen del tipo de dato, y son las que nos dan los valores “verdadosos” y “falsosos”:

  • undefined: se interpreta como falso. Un miembro de un objeto que no existe, por ejemplo.
  • Null: sería falso también.
  • NaN: falso
  • Un número: si es 0 se interpreta como falso, siendo verdadero para cualquier otro valor.
  • Cadenas de texto: si la cadena está vacía (“”), entonces es falso. Cualquier otra cadena se interpreta como un verdadero.
  • Un objeto cualquiera siempre se interpreta como true.

Es decir son valores:

  • falsosos o falsy: el número 0, las cadenas vacías, los resultados de operaciones no válidas (NaN: not a Number) los valores nulos y los valores no definidos.
  • verdadosos o truly: cualquier otra cosa (los números distintos de cero, las cadenas no vacías, cualquier objeto…)

Es gracias a esto que, por ejemplo, podemos verificar la existencia de ciertas características en un navegador escribiendo cosas como:

if (window.Worker)

    alert("Este navegador soporta Web Workers");

else

    alert("Navegador viejo!! Actualízate!!");

Si la ventana tiene el objeto Worker lo devuelve y al convertirlo en booleano, según las reglas anteriores, el resultado del condicional será true. Si no existe devolverá undefined, y por lo tanto se interpretara como un false.

¿Cuándo se interpreta un valor como booleano?

Bueno, obviamente cuando ya es booleano, claro :-P, pero la conversión implícita que hemos visto de valores “verdadosos” o “falsosos” en verdaderos booleanos sólo se da cuando se evalúa el valor dentro de cierto contexto. En concreto cuando se evalúa como resultado de una expresión en un condicional (lo hemos visto más arriba), pero también cuando se utiliza con un operador booleano (apartado 11.11 de la especificación ECMAScript)

Y este detalle es muy importante porque si no lo tenemos en cuenta podemos acabar cometiendo errores difíciles de detectar.

Por ejemplo, si variamos ligeramente el código del condicional anterior y ponemos una comparación en la expresión dentro del if:

var v = "cualquier cosa";

if (v == true)

    alert("verdadero");

else

    alert("falso");

Aparentemente no ha cambiado demasiado. Cabría pensar que como v contiene una cadena no vacía (un valor verdadoso) el condicional sigue siendo cierto. Sin embargo lo que veremos por pantalla será “falso”. El motivo es que ahora lo que estamos haciendo es comparar el valor de la variable v, que es una cadena, con un booleano para ver si son iguales. Y no lo son. Por lo tanto el resultado de la expresión es false. En este caso no se hace una conversión a booleano de la cadena aunque esté dentro de un condicional. Lo que se convierte siempre a booleano es el resultado de la expresión dentro del if, pero no cada elemento de la misma. Importante diferencia.

Sin embargo vamos a cambiarla nuevamente de manera sutil. ¿qué pasaría si utilizamos un operador booleano (AND, OR…) dentro de la expresión?:

var v = "cualquier cosa";

if (v && true)

    alert("verdadero");

else

    alert("falso");

Lo que hemos hecho es comparar el valor v con true usando un operador AND (&&). Según la lógica es equivalente a comprobar la igualdad, ya que el AND solo devuelve true si ambas partes son true. En este caso veríamos por pantalla la palabra “verdadero” y no “falso” como en el anterior. ¿Por qué? El motivo es que en este caso el valor de la variable v se convierte a booleano antes de hacer la operación, ya que estamos en el contexto de un operador lógico booleano.

El orden de los operadores importa cuando manejamos “verdadosos” y “falsosos”

Pero no se vayan todavía, que aún hay más… Vamos a darle una vuelta de tuerca más al asunto, con algo que seguro que a más de uno le puede sorprender.

¿Qué crees que devolverá por pantalla esta expresión?:

alert("cualquier cosa" && true);

Efectivamente, veremos aparecer “true” por pantalla pues la expresión se evalúa como verdadera al ser ambos valores “verdadosos”.

Si variamos ligeramente la expresión:

alert( true && "cualquier cosa");

Es exactamente la misma comparación, así que deberíamos ver lo mismo ¿no?: ¡No!, en este caso veremos por pantalla la frase “cualquier cosa” y no un “true” aunque el resultado de la operación es un verdadero al ser ambos valores “verdadosos”.

¿Qué ha pasado aquí?

Para comprenderlo hay que ver bien lo que pone la especificación acerca de cómo evaluar operadores lógicos booleanos.

En el caso del operador AND (&&), la especificación dice que se evalúa de la siguiente manera (resumiendo las pasos en una sola frase):

  • Si el valor convertido a booleano del primer operando es false, entonces devolver el primer operando, sino devolver el segundo operando.

En el caso del operador lógico OR (||) la regla es parecida:

  • Si el valor convertido a booleano del primer operando es true, entonces devolver el primer operando, sino devolver el segundo operando.

Así de sencillo pero con muchas implicaciones:

  1. En primer lugar, los operadores lógicos AND y OR no devuelven siempre un booleano, como casi todo el mundo piensa. Devuelven el valor del primer o del segundo operando dependiendo de si son “verdadosos” o “falsosos”. Solo devuelven un booleano si el operando que devuelven lo es.
  2. Se hace cortocircuito de expresiones, es decir, que llega con evaluar el primero si con eso ya sabemos cuál va a ser el resultado. Así, en un AND, si el primero es false ya no hace falta seguir evaluando pues tendrían que ser los dos true y ya es imposible. En el caso de OR si el primero es true ya no hace falta seguir evaluando porque si uno de ellos es true el resultado será true.

Si nos fijamos en nuestras expresiones anteriores a la luz de estas reglas, entonces vemos que tienen toda la lógica del mundo.

En el primer caso (“cualquier cosa” && true) el primer operador es verdadero, así que se devuelve directamente el segundo. Al darles la vuelta (true && “cualquier cosa”), pasa lo mismo y se devuelve directamente el segundo argumento, que en este caso es una cadena.

¿Cómo asegurarnos de que siempre vamos a trabajar con verdaderos booleanos?

Es fácil ver que toda esta casuística es muy peligrosa cuando trabajamos con valores que no son verdaderos booleanos, es decir, que se pueden interpretar como tales o no dependiendo del caso y de donde los usemos.

Para solucionarlo podemos usar una técnica muy sencilla que consiste en asegurarnos de devolver siempre todos los valores de nuestras funciones que deban ser booleanos convertidos en booleanos.

¿Y cómo los convertimos?

El operador ToBoolean es interno y no podemos llamarlo, así que podemos hacerlo de dos maneras: usando un objeto de tipo Boolean o utilizar el operador de negación dos veces.

La primera técnica es farragosa e ineficiente implica crear un objeto a partir de un tipo primitivo:

var b = Boolean("cualquier cosa");

alert(b);

La segunda es mucho más elegante y concisa y la verás utilizada por ahí bastante en código profesional. Se basa en que el uso del operador negación (!) convierte cualquier valor en booleano y lo niega, devolviendo un verdadero booleano. Si lo aplicamos dos veces, lo vuelves a negar y obtienes el valor original convertido en booleano:

var b = !!"cualquier cosa";

alert(b);

Así, si quieres asegurarte de que las expresiones anteriores devuelven un booleano siempre, puedes escribirlas así. Por ejemplo, el comparador que hicimos antes entre una cadena y un booleano dentro de un if, lo podemos transformar así:

var v = "cualquier cosa";

if (!!v == true)

    alert("verdadero");

else

    alert("falso");

y funcionará como un booleano de verdad, devolviendo un “verdadero” como esperábamos.

Sin embargo es una tontería aplicarlo a los operadores de la condición un if cuando se usan con operadores lógicos booleanos:

var v = "cualquier cosa";

if (!!v && true)    //Esto no sirve para nada

    alert("verdadero");

else

    alert("falso");

Ya que como hemos visto el if ya lo convierte siempre a un booleano. Esto lo verás muchas veces por ahí aplicado de esta manera y es una mala práctica porque no aporta absolutamente nada y aumenta la ineficiencia del código.

Una buena práctica sin embargo es asegurarnos de que lo que devuelve una función nuestra en la que se espera obtener siempre un booleano como resultado, usando el operador negación dos veces (!!) para ello:

return !!res;

De este modo aunque los datos originales sobre los que trabajamos no sean booleanos, nuestra función va devolver siempre uno. Esto es especialmente importante cuando trabajamos con elementos del DOM, que en un momento dado pueden no existir o carecer de alguna propiedad que estamos utilizando en el código. De hecho bibliotecas como jQuery hacen esto constantemente para devolver valores en sus funciones.

double negativeEn resumen

Una cosa aparentemente trivial como parece ser el trabajo con booleanos y pseudo-booleanos (verdadosos o truly y falsosos o falsy) tiene muchos pequeños detalles que debemos conocer para evitar desagradables sorpresas. Es el precio que tenemos que pagar a cambio de la flexibilidad que nos proporciona un lenguaje débilmente tipado como es JavaScript.

Si vamos a trabajar con valores que suponemos que son booleanos en comparaciones o para devolverlos como resultados de funciones es recomendable aplicarles una doble negación (!!) para asegurarnos de que los hemos convertido en booleanos verdaderos antes de usarlos. También debemos tener cuidado con los operadores lógicos booleanos (&& y ||) porque no siempre devuelven verdadero o falso, sino que dependiendo de los operando que utilicemos y de si estos son verdaderos booleanos o no, el resultado podría ser cualquier cosa.

¡Espero que te resulte interesante!

Para saber más: Fundamentos de JavaScript y AJAX para desarrolladores y diseñadores web

Si vas a compartir este post: por favor utiliza la URL original en JASoft: http://www.jasoft.org/Blog/post/Booleanos-pseudo-booleanos-operadores-y-condicionales-en-JavaScript-mucho-mas-complejo-de-lo-que-parece.aspx

Sin categoría

Deja un comentario

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *