Operator precedence and associativity
Multiple operations in a sequence
Multiple operations can be used in a sequence. The same operators or different operators can be used in the sequence.
In what order are these expressions executed?
In the next example the first sequence involves multiple equal operators (??
).
The following two sequences involve multiple different operators (??
and +
).
One uses parentheses, the other does not.
const a = undefined,
b = null,
c = "Hello";
console.log(a ?? b ?? c ?? "none"); // logs: "Hello"
console.log((a ?? "One") + (b ?? "Two") + (c ?? "Three")); // logs: "OneTwoHello"
console.log(a ?? "One" + b ?? "Two" + c ?? "Three"); // logs: "Onenull"
Operator precedence
Operator precedence determines the order of operations when evaluating a given expression. Operators with higher precedence become the operands of operators with lower precedence.
As in mathematics, exponentiation has higher precedence than both multiplication and division and they, in turn, have both higher precedence than both addition and subtraction. Comparison operators have higher precedence over logical operators. This operator precedence table lists all operators in order from highest precedence to lowest precedence.
let varA, varB, x = 9, y = 11;
console.log(2 + 3 * 5); // logs: 17 // 2 + (3 * 5) = 2 + 15 = 17
console.log(50 - 4 * 3 ** 2); // logs: 14 // 50 - (4 * (3 ** 2)) = 50 - (4 * 9) = 50 - 36 = 14.
console.log(x < 10 && y > 10); // logs: true // (x < 10) && (y > 10) // "true && true" returns true
console.log(varA ?? 2 * varB ?? 3); // logs: NaN // varA ?? (2 * varB) ?? 3
console.log( (varA ?? 2) * (varB ?? 3) ); // logs: 6 // 2 * 3
The precedence can be changed by using parentheses. Parts of an expression in parentheses are interpreted as an operand on its own.
let result = 0;
console.log( 40-(4*(2+(2**3))) === result &&
40-(4*(2+8)) === result &&
40-(4*10) === result ); // logs: true
Operator associativity
With operators on different precedence levels, associativity does not matter.
Associativity only comes into play when there are multiple operators of the same precedence.
In the
operator precedence table
there is also a column indicating the associativity of each operator.
It indicates the "direction of processing".
Left-associative operators are processed left-to-right (as (a OP1 b) OP2 c
)
and right-associative operators are processed right-to-left (as a OP1 (b OP2 c)
).
Operators on the same precedence level all have the same associativity (if associativity is applicable).
console.log( 5-2-1 === (5-2)-1 ); // logs: true // subtraction is left-associative
console.log( 2**3**4 === 2**(3**4) ); // logs: true // Exponentiation is right-associative
console.log( 5-2-1 === 5-(2-1) ); // logs: false
console.log( 2**3**4 === (2**3)**4 ); // logs: false
console.log(5 - 3 + 2); // logs: 4 // same precedence level, both left-associative: (5 - 3) + 2 = 2 + 2 = 4.
console.log(1 < 3 > 2); // logs: false // same precedence level, both left-associative: 1 < 3 is true, which is converted to 1; 1 > 2 is false.
console.log( 1 < 3 && 3 > 2 ); // logs: true // different precedence levels: (1 < 3) && (3 > 2)
let varA, varB;
console.log(varA = varB = 5); // logs: 5 // Assignment operators are right-associative: varA = (varB = 5) thus it logs the return value of varB = 5.
Expressions in JS are always evaluated left-to-right, regardless of associativity and precedence.
In the next example the function calls are executed left-to-right,
but the exponentiation (2**(3**2)
) is processed right-to-left, according to its associativity.
function operand(id, value){
console.log("Evaluating function " + id);
return value;
}
console.log( operand("one",2) ** operand("two",3) ** operand("three",2) ); // 2**(3**2) = 2**9 = 512.
Output to the console:
Evaluating function one Evaluating function two Evaluating function three 512
Although we could rely on the precedence and associativity rules, using parentheses to explicitly define the order of operations generally results in clearer code and is generally considered best practice.
However, what is within parentheses is not always evaluated first when dealing with short-circuit evaluation.
let a = true;
console.log( a || (b * c) ); // logs: true // b * c was not evaluated at all, even though it is in parentheses