Operadores avanzados

Operadores avanzados en Javascript


En la primera parte de este artículo, vimos una serie de operadores básicos en Javascript, donde explicamos, entre otros, operadores aritméticos, de asignación, unarios, operadores de comparación y operadores binarios. Como puedes ver, en general, para trabajar con valores numéricos.

En este artículo vamos a seguir con los operadores de Javascript, pero entrando en temas un poco más avanzados, y quizás menos intuitivos y más complejos:

  • Operadores de Strings: Operaciones con variables y/o
  • Operadores lógicos: Como trabajar con valores o similares
  • Otros operadores: Otros operadores sin relación directa con los apartados anteriores

Operadores de Strings

Al igual que tenemos operadores para trabajar con valores numéricos, en Javascript también tenemos operadores que se pueden utilizar con valores que no son numéricos, por ejemplo, con . Echemos un vistazo a las siguientes:

Nombre Operador Descripción
Concatenación de texto a + b Une el contenido de a con el contenido de b
Conversión a número (Suma unaria) +a Si a no es un número, intenta convertirlo en un número.

Profundicemos un poco en cada caso para entenderlos mejor y saber bien como funcionan.

Operador de concatenación

Anteriormente habíamos mencionado que el operador + se utiliza para realizar operaciones sumatorias. Esto es así cuando los dos operandos que utilizamos son números . Pero, ¿qué ocurre si no es así? En el siguiente ejemplo, puedes comprobar que ocurre cuando utilizamos el operador + y alguno de nuestros operandos no es un :

Ejemplo       Resultado                       Explicación
---------     ----------                      ------------
2 + 2         // 4      (Número + número)     2 + 2
"2" + "2"     // "22"   (String + string)     String(2) + String(2)
"2" + 2       // "22"   (String + número)     String(2) + 2
2 + "2"       // "22"   (Número + string)     2 + String(2)
"a" + 2       // "a2"   (String + número)     String("a") + 2

Observa que salvo en el primer caso (donde tenemos dos ), el operador + funciona como un concatenador, es decir, uniendo los dos , y en el caso que uno de ellos no lo sea, lo convierte.

Esto puede complicarse aún más si vamos usando operandos con diferentes tipos de datos, pero veremos eso un poco más adelante. Esto ocurre porque Javascript realiza lo que se llama un proceso de conversión implícita donde traduce los tipos de datos al que considera más oportuno. Muchas veces podrás encontrarla mencionada como Coerción.

Muchos programadores prefieren usar lenguajes de programación donde el sistema de tipos es más estricto que en Javascript (que por naturaleza es muy flexible). Si piensas de la misma forma, probablemente prefieras Typescript, un metalenguaje donde el programador debe indicar el tipo de dato que va a usar y posteriormente se traduce a JS.

Operador de suma unaria

Anteriormente, ya habíamos hablado del operador de resta unaria (negación) que sirve para cambiar de signo a un número. Sin embargo, también tenemos un operador de suma unaria que hace justo lo contrario: mantener positivo un número.

¿Qué sentido puede tener esto si un número, por defecto, ya es positivo? Ninguna. Por eso, en Javascript, el operador + se utiliza para forzar el cambio de tipo de dato a número, como podemos ver en el siguiente ejemplo:

+5              // 5      (El valor ya era numérico y positivo)
+-5             // -5     (El valor ya era numérico y negativo)
+"5"            // 5      (El valor era string y pasa a ser numérico)
+"-5"           // -5     (El valor era string y pasa a ser numérico)
+"a"            // NaN    (El valor era string pero no es un número)

De esta forma, al incluir el operador unario + previo a la variable, forzamos a convertirlo a número (o a su forma de representarse numéricamente).

Operadores lógicos

Los operadores lógicos son muy utilizados en su forma básica, sin embargo, tienen bastantes particularidades y matices, que intentaré explicar en esta sección. Dentro del apartado de operadores lógicos tenemos los siguientes:

Nombre Operador Descripción
Operador lógico AND a && b Devuelve a si es false, sino devuelve b.
Operador ternario ?: a ? b : c Si a es true, devuelve b, sino devuelve c.
Operador lógico OR a || b Devuelve a si es true, sino devuelve b.
Operador lógico Nullish coalescing a ?? b Devuelve b si a es null o undefined, sino devuelve a.
Operador de asignación lógica nula ??= a ??= b Es equivalente a a ?? (a = b)
Operador de encadenamiento opcional ?. data?.name Permite intentar acceder a una propiedad, aunque su padre no exista.
Operador unario lógico NOT !a Invierte el valor. Si es true devuelve false y viceversa.

Operador AND lógico

El operador lógico AND establece una condición donde devolverá el primer valor si es false, o el segundo valor si el primero es true. Esto se puede leer de forma que «devuelve b si a y b son verdaderos, sino a».

Veamos algunos ejemplos:

false && false      // false (si ninguno de los dos es true, false)
true && false       // false (idem)
false && true       // false (idem)
true && true        // true (si ambos son true, true)

Pero de la misma forma que el anterior, se puede utilizar con otros tipos de datos. Ahora si importa el primer valor y el segundo:

0 && undefined      // 0 (se evalua como false && false, devuelve el primero)
undefined && 0      // undefined (se evalua como false && false, devuelve el primero)
55 && null          // null (se evalua como true && false, devuelve el segundo)
null && 55          // null (se evalua como false && true, devuelve el primero)
44 && 20            // 20 (se evalua como true && true, devuelve el segundo)

Teniendo todo esto en cuenta, este operador es una oportunidad fantástica para utilizarlo a modo de if compactos y muy legibles. No obstante, ten en cuenta que este patrón puede ser interesante en algunos casos simples, pero en otros puede complicar la legibilidad de código.

Veamos algunos ejemplos de como utilizarlo:

45 && "OK"            // "OK"
false && "OK"         // false

const doTask = () => "OK!";   // Creamos función que devuelve "OK!"
isCorrect && doTask()         // Si isCorrect es true, ejecuta doTask()

También ten en cuenta que en el caso de necesitar un caso else para el fin, este patrón no sería ideal. Para ese caso, echa un vistazo al siguiente, el operador ternario.

Operador ternario

El operador ternario es una especie de if compacto que tienen la mayoría de los lenguajes de programación, donde en lugar de utilizar un if / else tradicional, para escribir en varias líneas, podemos hacerlo mediante el operador ternario. Su estructura es la siguiente: (condición) ? valor verdadero : valor falso.

Veamos como la utilizaríamos, comparándolo con un if:

// Sin operador ternario
let role;
if (name === "Manz") {
  role = "streamer";
} else {
  role = "user";
}

// Con operador ternario
const role = name === "Manz" ? "streamer" : "user";

En este caso, name === "Manz" sería la condición, "streamer" el valor si la condición es verdadera, y "user" el valor si la condición es falsa. Como puedes ver, el operador ternario permite escribir código mucho más compacto en muchas situaciones.

Condicionales y operador ternario

Operador OR lógico

El operador lógico OR establece una condición donde devolverá el primer valor si es true, o el segundo valor si el primero es false. Esto se puede leer de forma que «devuelve a (si es verdadero), o si no, b».

Veamos algunos ejemplos:

false || false     // false (si ninguno de los dos es true, false)
true || false      // true (desde que uno sea true, true)
false || true      // true (idem)
true || true       // true (idem)

Sin embargo, ten en cuenta que podemos utilizarlo con otros tipos de datos. En el anterior, los valores repetidos no hay que diferenciarlos: si false || false te da igual que false devuelva. Eso no ocurre con otros tipos de datos. Recuerda que cualquier valor superior a 0 es considerado true como y que cualquier valor que sea 0 o falsy, es false.

Veamos algunos ejemplos:

0 || null          // null (se evalua como false || false, devuelve el segundo)
44 || undefined    // 44 (se evalua como true || false, devuelve el primero)
0 || 17            // 17 (se evalua como false || true, devuelve el segundo)
4 || 10            // 4 (se evalua como true || true, devuelve el primero)

Teniendo todo esto en cuenta, el operador || nos podría venir bastante bien para situaciones donde, por ejemplo, tenemos una variable name que no sabemos a ciencia cierta si está definida y queremos crear una nueva variable userName con el valor de name, o sino, un valor por defecto "Unknown name":

const userName = name || "Unknown name";

"Manz" || "Unknown name"      // "Manz"
null || "Unknown name"        // "Unknown name"
false || "Unknown name"       // "Unknown name"
undefined || "Unknown name"   // "Unknown name"
0 || "Unknown name"           // "Unknown name"

OJO: Ten presente que en algunos casos te puede interesar esta funcionalidad, pero 0 es un valor válido para ti, cosa que en este caso no se permite (como se puede ver en el último caso). Para ello, es mejor utilizar el operador ?? de unión nula (nullish coalescing).

Operador Nullish coalescing

El operador nullish coalescing (unión nula) es un operador lógico muy similar al operador OR, pero con ciertos matices y cambios. A grandes rasgos, se puede decir que el operador a ?? b devuelve b sólo cuando a es undefined o null. Al contrario que el operador OR, con valores como false, 0 o "", no devuelve b.

Veamoslo con un ejemplo para ver la diferencia con el anterior:

42 || 50          // 42
42 ?? 50          // 42 (ambos se comportan igual)
false || 50       // 50 (false es un valor falsy, devuelve el segundo)
false ?? 50       // false (la parte izquierda no es null ni undefined, devuelve el primero)
0 || 50           // 50 (0 es un valor falsy, devuelve el segundo)
0 ?? 50           // 0 (la parte izquierda no es null ni undefined, devuelve el primero)
null || 50        // 50 (null es un valor falsy, devuelve el segundo)
null ?? 50        // 50 (devuelve el segundo)
undefined || 50   // 50 (undefined es un valor falsy, devuelve el segundo)
undefined ?? 50   // 50 (devuelve el segundo)

Dependiendo del caso, podría interesarnos utilizar el operador ?? o el operador ||. Por ejemplo, imagina que tenemos un objeto data donde tenemos almacenado la cantidad de balas que le quedan a un personaje.

Si necesitamos mostrar al usuario visualmente en el menú que se ha quedado sin balas, quizás nos podría interesar utilizar el operador ||. Por otro lado, si lo que queremos es sumar la cantidad de balas que tiene, con la cantidad de proyectiles, quizás nos interesaría más utilizar el operador ??.

const data = { ammo: 0 }
data.ammo || "Sin balas";     // "Sin balas"
data.ammo ?? "Sin balas";     // 0

const data = {}
data.ammo || "Sin balas";     // "Sin balas"
data.ammo ?? "Sin balas";     // "Sin balas"

Ten en cuenta que en el segundo caso, la propiedad ammo es undefined, ya que no está definida.

Asignación lógica nula

Este operador es bastante interesante para algunas operaciones muy frecuentes en Javascript. Existen ciertos casos donde, si una variable tiene valores null o undefined (valores nullish) y sólo en esos casos, queremos cambiar su valor. Veamos como se haría sin utilizar la asignación lógica nula y como podríamos resumirlo utilizándola:

// Sin asignación lógica nula
if (x === null || x === undefined) {
  x = 50;
}

// Con asignación lógica nula
x ??= 50;

Recuerda que a ??= b es equivalente a a ?? (a = b). Esto puede ser super útil para simplificar casos como el siguiente:

const resetConfig = (data) => {
  data.life ??= 100;
  data.level ??= 1;
  return data;
}

resetConfig({ life: 25, level: 4 });      // { life: 25, level: 4 }
resetConfig({ life: null, level: 2 });    // { life: 100, level: 2 }
resetConfig({});                          // { life: 100, level: 1 }

Observa que la función resetConfig() obtiene un objeto por parámetro y en el caso de tener una de las propiedades life o level a null o no existir (o valer undefined), las reseteará al valor indicado.

Encadenamiento opcional

Existe un operador muy interesante denominado optional chaining (operador de encadenamiento opcional). Este operador nos permite acceder a propiedades, aunque su elemento padre no exista, de forma que podamos evitar un error que es bastante frecuente.

Veámoslo con un ejemplo, para situarnos mejor. Tenemos el siguiente objeto:

const user = {
  name: "Manz",
  role: "streamer",
  attrs: {
    life: 100,
    power: 25
  }
}

Si intentamos acceder a una de sus propiedades en el interior de attrs lo haremos sin problema:

user.attrs.power     // 25
user.attrs.life      // 100

Sin embargo, vamos a imaginar que la propiedad attrs (y todo su contenido) no existe aún, sino que tenemos un objeto user que solo tiene las propiedades name y role, pero que en algún momento de nuestro código esta propiedad attrs se añadirá en nuestro objeto.

Si intentamos acceder a una de sus propiedades sin que la propiedad attrs exista, ocurriría lo siguiente:

user.attrs.power     // TypeError: Cannot read properties of undefined (reading 'power')
user.attrs.life      // TypeError: Cannot read properties of undefined (reading 'life')

user.attrs && user.attrs.life   // Evitamos el error (comprobamos si existe attrs antes)

Esto ocurre porque estamos intentando acceder a la propiedad power o life dentro de una propiedad attrs que no está definida, es decir, que es undefined. Dicho de otro modo, estamos intentando hacer un undefined.power o un undefined.life.

Para evitar esto, hasta ahora teníamos que utilizar un if condicional, un operador lógico AND o alguna forma similar que comprobara antes que attrs existe realmente, volviendolo bastante engorroso si tenemos múltiples objetos anidados uno dentro de otro. Ahora podemos utilizar el optional chaining, que no es más que añadir una ? antes del punto de la propiedad a la que queremos acceder:

user.attrs?.power    // undefined
user.attrs?.life     // undefined

Como puedes ver, ahora podemos hacer el intento de acceso sin que nos lance un error. Nos devuelve undefined porque no está definido, pero podemos acceder de forma segura.

Operador lógico NOT

El operador lógico NOT es un operador unario que se utiliza para negar un valor, es decir, para invertir su valor . Si una variable vale true, al negarla valdrá false y si una variable vale false, al negarla, valdrá false. Para negar una variable, se precede del símbolo !.

Veamos algunos ejemplos:

!true        // false
!false       // true
!!true       // true
!!false      // false
!!!true      // false

Sin embargo, también lo podemos hacer con variables con otros tipos de datos:

!44          // false (se evalua como !true)
!0           // true (se evalua como !false)
!""          // true (es lo mismo que !0, que a su vez es !false)
!(10 || 23)  // false (se evalua como !10, que es !true)

Observa que, como se aprecia en el último caso, también podemos usarlo con expresiones más complejas.

Otros operadores

Existen otros operadores menos frecuentes como los que incluyo en esta sección, que merece la pena echar un vistazo por conocimiento general.

Operador coma

Quizás, puede parecer el más extraño de todos, ya que no se suele usar demasiado de forma individual, pero en algunos contextos es muy utilizado. El operador coma se utiliza simplemente para realizar varias operaciones o sentencias en una misma linea, separándolas por comas. Veamos un ejemplo muy sencillo:

// Sin operador coma
const a = 5;
const b = 10;

// Con operador coma
const a = 5, b = 10;

Ten en cuenta que en una misma línea, y separando por coma, estamos realizando dos operaciones: declarando dos constantes. Ahora, observa el siguiente ejemplo donde vamos a hacer múltiples asignaciones. Si lo haces desde la consola del navegador, comprobarás que sólo devuelve el resultado de la última operación:

1 + 2, 1 + 1, 3 + 3                // 6 (Parece que sólo ha hecho la tercera operación)
a = 1 + 2, b = 1 + 1, c = 3 + 3    // a=3, b=2, c=6 (se han realizado todas)

Observa que en el primer caso puede parecer que ocurre sólo la última parte, porque devuelve 6. Sin embargo, se han ejecutado todas las operaciones, como se puede ver en el segundo ejemplo, comprobando los valores almacenados en a, b y c. Otro ejemplo:

alert("Hello"), alert("Hi"), alert("Bye"), "42"   // Se ejecutan todos, pero devuelve "42"

¿Quién soy yo?

Soy Manz, vivo en Tenerife (España) y soy streamer partner en Twitch y profesor. Me apasiona el universo de la programación web, el diseño y desarrollo web y la tecnología en general. Aunque soy full-stack, mi pasión es el front-end, la terminal y crear cosas divertidas y locas.

Puedes encontrar más sobre mi en Manz.dev