Precedencia de operadores
Introducción
Cuando escribimos expresiones en JavaScript que contienen múltiples operadores, es fundamental entender cómo el lenguaje determina el orden en que se evaluarán dichos operadores. Este concepto se conoce como "precedencia de operadores" y afecta directamente el resultado de nuestras operaciones. Así como en matemáticas multiplicamos antes de sumar, en JavaScript existe un conjunto de reglas que determinan qué operaciones se realizan primero.
En este artículo exploraremos qué es la precedencia de operadores, cómo funciona en JavaScript, y cómo podemos controlar el orden de evaluación para asegurarnos de que nuestras expresiones se calculen exactamente como deseamos. Dominar este concepto te permitirá escribir código más predecible y evitar errores sutiles que pueden ser difíciles de detectar.
Concepto de precedencia
La precedencia de operadores determina el orden en el que se evalúan las operaciones en una expresión. Los operadores con mayor precedencia se ejecutan antes que los de menor precedencia. Cuando los operadores tienen la misma precedencia, generalmente se evalúan de izquierda a derecha (aunque hay excepciones que veremos más adelante).
Veamos un ejemplo sencillo:
let resultado = 2 + 3 * 4;
console.log(resultado); // 14, no 20
En esta expresión, la multiplicación (*
) tiene mayor precedencia que la suma (+
), por lo que primero se calcula 3 * 4
(que es 12) y luego se suma 2, obteniendo 14. Si la evaluación fuera estrictamente de izquierda a derecha sin considerar la precedencia, el resultado sería (2 + 3) * 4 = 20
, lo cual es incorrecto.
Tabla de precedencia de operadores
JavaScript tiene una tabla de precedencia bien definida. Aquí presentamos los operadores más comunes, ordenados de mayor a menor precedencia:
Orden | Categoría | Operadores | Asociatividad |
---|---|---|---|
1 | Agrupación | ( ... ) |
n/a |
2 | Acceso a miembros / índices | . [ ... ] |
De izquierda a derecha |
3 | Llamada a función | ... ( ... ) |
De izquierda a derecha |
4 | Operadores de incremento/decremento | ++ -- |
n/a |
5 | Negación lógica / Negación unitaria | ! - (negativo) + (positivo) typeof |
De derecha a izquierda |
6 | Exponenciación | ** |
De derecha a izquierda |
7 | Multiplicación / División / Módulo | * / % |
De izquierda a derecha |
8 | Suma / Resta | + - |
De izquierda a derecha |
9 | Operadores de comparación | < <= > >= instanceof in |
De izquierda a derecha |
10 | Igualdad | == != === !== |
De izquierda a derecha |
11 | AND lógico | && |
De izquierda a derecha |
12 | OR lógico | || |
De izquierda a derecha |
13 | Nullish coalescing | ?? |
De izquierda a derecha |
14 | Operador condicional (ternario) | ... ? ... : ... |
De derecha a izquierda |
15 | Asignación | = += -= *= /= etc. |
De derecha a izquierda |
16 | Coma | , |
De izquierda a derecha |
Esta tabla es una versión simplificada. Para casos específicos o operadores menos comunes, es recomendable consultar la documentación oficial de JavaScript.
Uso de paréntesis para control explícito
Los paréntesis ( )
tienen la precedencia más alta en JavaScript y nos permiten anular el orden predeterminado de evaluación. Cualquier expresión dentro de paréntesis se evaluará primero, independientemente de la precedencia de los operadores que contenga.
// Sin paréntesis: se aplica la precedencia predeterminada
let a = 2 + 3 * 4; // 14 (3 * 4 = 12, luego 2 + 12 = 14)
// Con paréntesis: controlamos el orden de evaluación
let b = (2 + 3) * 4; // 20 (2 + 3 = 5, luego 5 * 4 = 20)
Los paréntesis se pueden anidar, y en ese caso la evaluación comienza desde los paréntesis más internos:
let c = (2 + (3 * 4)) / 2; // 7 (3 * 4 = 12, luego 2 + 12 = 14, luego 14 / 2 = 7)
Es una buena práctica usar paréntesis para clarificar el orden de operaciones, incluso cuando no sean estrictamente necesarios. Esto hace que el código sea más legible y reduce la probabilidad de errores.
Evaluación de expresiones complejas
Veamos algunos ejemplos más complejos para entender cómo JavaScript evalúa expresiones que combinan diferentes tipos de operadores:
Operadores aritméticos y de comparación
let resultado = 5 + 10 > 3 * 5;
console.log(resultado); // false
// Paso a paso:
// 1. 5 + 10 = 15 (suma)
// 2. 3 * 5 = 15 (multiplicación)
// 3. 15 > 15 (comparación)
// 4. false (resultado)
Operadores lógicos
let x = 5;
let y = 10;
let z = 15;
let resultado = x < y && y < z || x > z;
console.log(resultado); // true
// Paso a paso:
// 1. x < y = 5 < 10 = true
// 2. y < z = 10 < 15 = true
// 3. true && true = true
// 4. x > z = 5 > 15 = false
// 5. true || false = true
Aquí, &&
tiene mayor precedencia que ||
, por lo que primero se evalúa x < y && y < z
(que da true
), y luego se evalúa true || x > z
(que también da true
).
Con paréntesis para alterar la precedencia
let x = 5;
let y = 10;
let z = 15;
let resultado = x < y && (y < z || x > z);
let resultadoAlternativo = (x < y && y < z) || x > z;
console.log(resultado); // true
console.log(resultadoAlternativo); // true (igual que sin paréntesis, porque ya
// sigue la precedencia natural)
En el primer caso, primero se evalúa la expresión entre paréntesis (y < z || x > z)
, que da true
. Luego se evalúa x < y && true
, que también da true
.
Casos especiales y errores comunes
Asociatividad de operadores
La asociatividad determina el orden de evaluación cuando hay múltiples operadores con la misma precedencia. La mayoría de los operadores en JavaScript tienen asociatividad de izquierda a derecha, lo que significa que se evalúan desde la izquierda hacia la derecha.
Sin embargo, algunos operadores como la asignación (=
) y la exponenciación (**
) tienen asociatividad de derecha a izquierda:
// Asociatividad de derecha a izquierda para operador de asignación
let a, b, c;
a = b = c = 5; // Equivalente a: a = (b = (c = 5))
console.log(a, b, c); // 5 5 5
// Asociatividad de derecha a izquierda para exponenciación
let resultado = 2 ** 3 ** 2; // Equivalente a: 2 ** (3 ** 2) = 2 ** 9 = 512
console.log(resultado); // 512, no 64 (que sería (2 ** 3) ** 2)
Error común: confundir precedencia en operadores lógicos y de comparación
Un error común es no recordar que los operadores de comparación (<
, >
, <=
, >=
, ==
, ===
, etc.) tienen mayor precedencia que los operadores lógicos (&&
, ||
):
// Intención: verificar si edad está entre 18 y 65
let edad = 30;
let esElegible = edad >= 18 && edad <= 65; // true (correcto)
// Error común
let esElegibleIncorrecto = 18 <= edad <= 65; // true, pero no por la razón esperada
// Paso a paso del error:
// 1. 18 <= edad = 18 <= 30 = true
// 2. true <= 65 = 1 <= 65 (true se convierte a 1) = true
// Esta expresión daría true incluso para edad = 100
Error común: operador ternario vs. operadores lógicos
El operador ternario tiene menor precedencia que la mayoría de los operadores lógicos y aritméticos:
// Intención: si a > b, entonces max = a, sino max = b
let a = 10, b = 5;
let max = a > b ? a : b; // 10 (correcto)
// Error común
let resultado = a > b ? a + b : a - b; // 15
// Lo que podría confundirse con:
let resultadoConfuso = a > b ? a : b + a; // 10, no 15
// Paso a paso:
// 1. a > b = 10 > 5 = true
// 2. Como es true, se evalúa la expresión después del ? y antes del :
// 3. a = 10
// No se evalúa la expresión después del : (b + a)
Asociatividad de operadores
La asociatividad es un aspecto importante de la precedencia de operadores que determina cómo se agrupan los operadores del mismo nivel de precedencia. Existen dos tipos principales:
Asociatividad de izquierda a derecha
La mayoría de los operadores en JavaScript tienen asociatividad de izquierda a derecha. Esto significa que las operaciones se agrupan y evalúan desde la izquierda hacia la derecha.
// Ejemplo con operador de suma (+)
let suma = 1 + 2 + 3; // (1 + 2) + 3 = 6
// Ejemplo con operadores de comparación
let comparacion = a < b > c; // (a < b) > c
Asociatividad de derecha a izquierda
Algunos operadores tienen asociatividad de derecha a izquierda, lo que significa que se agrupan y evalúan desde la derecha hacia la izquierda:
// Ejemplo con operador de asignación (=)
let a, b, c;
a = b = c = 5; // a = (b = (c = 5))
// Ejemplo con operador de exponenciación (**)
let potencia = 2 ** 3 ** 2; // 2 ** (3 ** 2) = 2 ** 9 = 512
Los principales operadores con asociatividad de derecha a izquierda son:
- Asignación (
=
,+=
,-=
, etc.) - Exponenciación (
**
) - Operador ternario (
?:
) - Operadores unarios (
!
,+
,-
,++
,--
, etc.)
Buenas prácticas para claridad de código
1. Usar paréntesis para clarificar la intención
Incluso cuando la precedencia predeterminada coincide con tu intención, los paréntesis pueden hacer que el código sea más legible:
// Sin paréntesis (pero correcto según la precedencia)
let resultado = a + b * c;
// Con paréntesis (más claro)
let resultadoClaro = a + (b * c);
2. Dividir expresiones complejas
Las expresiones largas o complejas pueden dividirse en partes más simples usando variables intermedias:
// Expresión compleja
let resultado = a * b + c * d / e - f ** g;
// Versión más clara con variables intermedias
let parte1 = a * b;
let parte2 = c * d / e;
let parte3 = f ** g;
let resultadoClaro = parte1 + parte2 - parte3;
3. Documentar decisiones no obvias
Si estás aprovechando intencionalmente la precedencia para algún propósito específico, puede ser útil añadir un comentario:
// Aprovechamos que && tiene mayor precedencia que || para evaluar esta condición
let esValido = a > 0 && b > 0 || c > 0 && d > 0; // (a>0 && b>0) || (c>0 && d>0)
4. Evitar confiar en precedencias poco conocidas
Algunas reglas de precedencia son menos conocidas y pueden causar confusión. Es mejor usar paréntesis en esos casos:
// Potencialmente confuso
let resultado = a && b || c && d;
// Más claro
let resultadoClaro = (a && b) || (c && d);
Resumen
La precedencia de operadores determina el orden en que JavaScript evalúa las expresiones que contienen múltiples operadores. Entender estas reglas es esencial para escribir código que se comporte como esperamos. Los operadores de mayor precedencia se evalúan primero, y cuando los operadores tienen la misma precedencia, la asociatividad (izquierda a derecha o derecha a izquierda) determina el orden.
Los paréntesis nos permiten anular la precedencia predeterminada y forzar un orden de evaluación específico. Utilizarlos adecuadamente no solo garantiza que nuestras expresiones se evalúen correctamente, sino que también hace nuestro código más legible y mantenible.
En el próximo artículo, entraremos en el mundo de las estructuras de control con la sentencia if/else
, que nos permitirá crear ramas en nuestro código según se cumplan o no determinadas condiciones.