Ir al contenido principal

Comparaciones y operadores lógicos

"En programación las comparaciones son sinceras: No hay matices, o es verdad… o no lo es.

En el capítulo anterior, Asignación, operadores aritméticos y concatenación, abordamos los operadores relativos al manejo de números y cadenas.

Este capítulo es el equivalente al capitulo anterior con valores booleanos, y presenta los operadores necesarios para operar este tipo de datos.

Además, y no menos importante, este capítulo aborda los operadores de comparación, que van a permitir determinar si dos valores numéricos, alfanuméricos o booleanos, son iguales o diferentes.

Índice

  1. Comparación
    1. Comparación abstracta
  2. Operadores lógicos
    1. Precedencia de operadores
    2. Conversión implícita
  3. Objetivos conseguidos
  4. Próximos pasos

Comparación

Comparar dos valores en JS, como en el resto de lenguajes de programación, es una operación que nos permite determinar si esos dos valores son iguales o diferentes.

Los operadores de comparación en JS son:

  • El operador ===, que devuelve true cuando dos valores son iguales.
  • El operador !==, que devuelve true cuando dos valores son diferentes.
// Operaciones con valores de tipo number
const one = 1
const two = 2

console.log(one === 1)		// Imprime "true"
console.log(one === two)	// Imprime "false"

// Operaciones con valores de tipo string
const foo = "foo"
const bar = "bar"

console.log(foo !== bar)	// Imprime "true"

const falsy = (foo !== foo)
console.log(falsy)		// Imprime "false"

// Operaciones con valores de tipo boolean
console.log(falsy === false)	// Imprime "true"

Como has podido ver en el ejemplo anterior, los operadores de comparación permiten operar con cualquiera de los tipos básicos expuestos hasta ahora (number, string y boolean).

Comparación abstracta

En capítulos anteriores hablamos ya de algunas cosas malas que tiene JS y de las que es mejor alejarse y no tocar.

Una de ellas son los comparadores abstractos, o de igualdad abstracta (loose equality en ingles), y que permiten realizar comparaciones no estrictas entre distintos valores:

  • El operador ==, que devuelve true cuando dos valores son no estrictamente iguales.
  • El operador !=, que devuelve true cuando dos valores son no estrictamente diferentes.

Los comparadores de igualdad abstracta se basan en unas reglas de conversión casi imposibles de recordar, lo que tarde o temprano provoca que lo que esperabas fuese igual en realidad no lo es, y viceversa.

Mi más sincero consejo, también expresado por Douglas Crockford en su libro Javascript: The good parts, es que huyas de los operadores de igualdad abstracta. Te aconsejo (aconsejamos) encarecidamente que bases tus operaciones en los operadores de comparación explicados al principio del capítulo.

No obstante, si quieres saber más de esos hijos de Caín, la referencia de MDN los describe con pelos y señales.

Operadores lógicos

JS define tres operadores lógicos, los más básicos:

  • El operador binario &&, que permite realizar un AND lógico sobre dos operandos booleanos.
  • El operador binario ||, que permite realizar un OR lógico sobre dos operandos booleanos.
  • El operador unario !, que permite realizar un NOT lógico sobre un valor booleano.
const truthy = true
const falsy = false

console.log(truthy || falsy)	// Imprime "true"
console.log(truthy && falsy)	// Imprime "false"
console.log(!truthy)		// Imprime "false"

JS no va más allá de los operadores binarios expuestos. Si quieres implementar operaciones binarias NAND, NOR, xOR o xNOR deberás hacerlo mediante combinaciones de los operadores anteriores:

  • NAND: !(a && b)
  • NOR: !(a || b)
  • xOR: (a && !b) || (!a && b)
  • xNOR: (a && b) || (!a && !b)

Precedencia de operadores

En cuanto a la precendecia de los operadores booleanos, se aplican estas reglas:

  1. Primero se aplica el operador unario NOT (!)
  2. Después se aplica el operador binaro AND (&&)
  3. Por último se aplica el operador binario OR (||)
const a = true
const b = false
const c = true

console.log(!a && b || c)	// Imprime "true"
console.log(!a || b && c )	// Imprime "false"

Y, como siempre, los paréntesis nos permiten ajustar esa precendencia:

const a = true
const b = false
const c = true

console.log((!a && b) || c)	// Imprime "true"
console.log(!a && (b || c))	// Imprime "false"

Conversión implícita

Como en el caso de los operadores aritméticos, JS es capaz de lleva a cabo conversiones implícitas cuando evalúa expresiones basadas en operadores booleanos, esto es, es capaz de silenciosamente convertir aquellos operandos no booleanos en su equivalente booleano antes de resolver la expresión.

Lamentablemente, las reglas de conversión no son tan simples como en el caso de los operadores aritméticos:

  • El operador unario NOT (!) siempre devuelve un valor booleano tras convertir su único operando a su equivalente booleano.
  • Los operadores binarios AND (&&) y OR (||) funcionan de un modo distinto. Si se utilizan operandos no booleanos, se evalúan como booleanos pero la expresión NO devuelve un valor booleano nunca:
    • El operador binario AND devuelve el primero operando si éste se evalúa como false, y devuelve el segundo operando en caso que el primer operando se evalúe como true.
    • El operador binario OR devuelve el primero operando si éste se evalúa como true, y devuelve el segundo operando en caso que el primer operando se evalúe como false.
const truthy = "Una cadena no vacía"
const falsy = 0

// Conversiones con operador !
console.log(!falsy)	// Imprime "true"
console.log(!truthy)	// Imprime "false"

// Conversiones con operador &&
console.log(falsy && falsy)	// Imprime "0"
console.log(falsy && truthy)	// Imprime "0"
console.log(truthy && falsy)	// Imprime "0"
console.log(truthy && truthy)	// Imprime "Una cadena no vacía"

// Conversiones con operador ||
console.log(falsy || falsy)	// Imprime "0"
console.log(falsy || truthy)	// Imprime "Una cadena no vacía"
console.log(truthy || falsy)	// Imprime "Una cadena no vacía"
console.log(truthy || truthy)	// Imprime "Una cadena no vacía"

Puede parecer algo complicado a primera vista, pero si se realiza la conversión a valor booleano de las expresiones devueltas anteriormente, vemos que las reglas de evaluación anteriores tienen todo el sentido del mundo:

const truthy = "Una cadena no vacía"
const falsy = 0

// Conversiones con operador &&
console.log(Boolean(falsy && falsy))	// Imprime "false"
console.log(Boolean(falsy && truthy))	// Imprime "false"
console.log(Boolean(truthy && falsy))	// Imprime "false"
console.log(Boolean(truthy && truthy))	// Imprime "true"

// Conversiones con operador ||
console.log(Boolean(falsy || falsy))	// Imprime "false"
console.log(Boolean(falsy || truthy))	// Imprime "true"
console.log(Boolean(truthy || falsy))	// Imprime "true"
console.log(Boolean(truthy || truthy))	// Imprime "true"

Por tanto aviso para navegantes: Cuidado con los valores devueltos por los operadores && y || cuando se evalúen operandos no booleanos en contextos no booleanos (aquellos que no convierten implícitamente el valor devuelto a boolean), ya que los resultados devueltos no serán booleanos.

El lío se plasma en el siguiente ejemplo en todo su esplendor:

const truthy = "Una cadena no vacía"
const truthyButNotTrue = truthy && truthy

console.log(truthyButNotTrue)	// Imprime "Una cadena no vacía"
console.log(Boolean(truthyButNotTrue))	// Imprime "true"
console.log(truthyButNotTrue === true)	// Imprime "false"

Cómo sobrevivir a la conversión implícita en booleanos

Como hemos visto, la conversión implícita en booleanos, específicamente con los operadores && y ||, puede llegar a ser compleja de gestionar y devolver resultados inesperados.

La razón es que en este tipo de conversión confluyen dos factores que, combinados, provocan confusión: por un lado es necesario recordar las reglas de conversión a booleanos y por otro es necesario tener en cuenta que los operadores && y || no devolverán un valor booleano, sino el valor del primer o segundo operando.

Esta trampa puede desactivarse de un modo sencillo y a la vez muy visual: combinar operaciones de igualdad con operaciones && y ||, tal como se muestra en el siguiente ejemplo:

const myString = "Una cadena no vacía"
const myNumber = 0

// En ambos casos se imprime "true",
// ya que una cadena no vacía equivale a true
console.log(Boolean(myString))
console.log(myString !== "")

// En ambos casos se imprime "false",
// ya que 0 equivale a false
console.log(Boolean(myNumber))
console.log(myNumber !== 0)

// Esta operación es confusa: imprime 
// el primer operando ("0")
// en lugar de un valor boolean
console.log(myNumber && myString)

// Esta operación es la misma operación que la anterior,
// pero utiliza los operadores de igualdad en lugar de
// la conversión implícita, imprimiendo siempre
// un valor booleano (en este caso "false")
console.log(myNumber !== 0 && myString !== "")

Me atrevería a decir que incluso a un programador senior se le permitiría prescindir de la conversión implícita para utilizar expresiones como la mostrada en el ejemplo anterior que, en mi opinión, son muchísimo más visuales y evitan confusión tanto al programador como a los miembros de su equipo.

Por tanto, mi recomendación para ti es que utilices los operadores de comparación (=== y !==) con junto a los operadores && y || y prescindas de la conversión implícita. Al menos hasta que seas senior.

Objetivos conseguidos

Al completar este capítulo, junto con el capítulo Asignación, operadores aritméticos y concatenación, has obtenido los conocimientos necesarios para comparar y operar los tipos básicos de datos en JS, boolean, number y string.

Específicamente, en este capítulo has aprendido a realizar operaciones booleanas de tipo AND, OR y NOT, así como a realizar operaciones booleanas no nativamente soportadas por JS mediante el uso de AND, OR y NOT.

Además has aprendido cómo comparar valores boolean, number o string en JS para determinar si son iguales o diferentes y se han expuesto las razones para huir de los operadores de igualdad abstracta.

Finalmente has aprendido las reglas de conversión implícita que aplican en las operaciones AND, OR y NOT, incluyendo los peligros de esperar resultados booleanos cuando en realidad algunas de esas operaciones con conversiones implícitas no los devuelven.

Próximos pasos

Ahora que dominas el uso de los operadores de comparación y operadores lógicos, estás preparado para aprender a utilizar una de las estructuras más básicas y a la vez más potentes de todos y cada uno de los lenguajes de progración y, por supuesto, de JS: los operadores condicionales, expuestos en el próximo capítulo del bloque de sintáxis básica.