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:
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
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.
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 +
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 +
funciona como un concatenador, es decir, uniendo los dos
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.
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).
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. |
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.
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
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 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).
El operador nullish coalescing 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 primero)
undefined || 50 // 50 (undefined es un valor falsy, devuelve el segundo)
undefined ?? 50 // 50 (devuelve el primero)
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.
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.
Existe un operador muy interesante denominado optional chaining
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.
El operador lógico NOT es un operador unario que se utiliza para negar un valor, es decir, para invertir su valor 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 (se evalua como !0, que 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.
Existen otros operadores menos frecuentes como los que incluyo en esta sección, que merece la pena echar un vistazo por conocimiento general.
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 ejmplo 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"
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