JavaScript, like many programming languages, follows a set of rules to determine the order in which operations are performed. This concept is known as operator precedence. Understanding these rules is crucial for writing correct and predictable code. In this comprehensive guide, we'll dive deep into JavaScript's operator precedence, exploring its intricacies with practical examples and real-world scenarios.
What is Operator Precedence?
Operator precedence determines the order in which operators are evaluated when an expression contains multiple operators. It's similar to the order of operations in mathematics (remember PEMDAS?). In JavaScript, operators with higher precedence are evaluated first.
🔑 Key Point: Operator precedence is not about the order in which code is executed from left to right, but rather about which operator is evaluated first in an expression.
Let's start with a simple example:
let result = 5 + 3 * 2;
console.log(result); // Output: 11
In this case, multiplication (*
) has higher precedence than addition (+
), so 3 * 2
is evaluated first, resulting in 6. Then, 5 is added to 6, giving us 11.
The Precedence Table
JavaScript has a well-defined precedence table. Here's a simplified version, ordered from highest precedence to lowest:
- Grouping
( )
- Member Access
.
[]
- Function Call
()
new
(with argument list)- Postfix Increment/Decrement
++
--
- Prefix Increment/Decrement
++
--
- Unary Operators
+
-
!
~
typeof
void
delete
- Exponentiation
**
- Multiplication/Division/Remainder
*
/
%
- Addition/Subtraction
+
-
- Bitwise Shift
<<
>>
>>>
- Relational
<
<=
>
>=
in
instanceof
- Equality
==
!=
===
!==
- Bitwise AND
&
- Bitwise XOR
^
- Bitwise OR
|
- Logical AND
&&
- Logical OR
||
- Conditional (Ternary)
?:
- Assignment
=
+=
-=
*=
etc. - Comma
,
Now, let's explore each of these categories with detailed examples.
1. Grouping ( )
Parentheses have the highest precedence in JavaScript. They allow you to override the default precedence rules and force a specific order of operations.
let a = 5 + 3 * 2; // 11
let b = (5 + 3) * 2; // 16
console.log(a, b);
In this example, a
is calculated as we saw earlier, but for b
, the parentheses force the addition to happen before the multiplication.
2. Member Access . []
Member access operators allow you to access properties of an object. They have very high precedence.
let person = {
name: "Alice",
age: 30,
hobbies: ["reading", "swimming"]
};
console.log(person.name); // "Alice"
console.log(person["age"]); // 30
console.log(person.hobbies[0]); // "reading"
In the last line, person.hobbies
is evaluated first (dot notation), then the bracket notation [0]
is applied to the resulting array.
3. Function Call ()
Function call parentheses have high precedence, which means they're evaluated before most other operations.
function double(x) {
return x * 2;
}
let result = double(5) + 3;
console.log(result); // 13
Here, double(5)
is evaluated first, returning 10, which is then added to 3.
4. new (with argument list)
The new
operator creates an instance of an object type. It has high precedence when used with arguments.
function Person(name) {
this.name = name;
}
let alice = new Person("Alice");
console.log(alice.name); // "Alice"
// Precedence in action
let bob = new Person("Bob").name;
console.log(bob); // "Bob"
In the last example, new Person("Bob")
is evaluated first, creating a new object, and then .name
is accessed on that object.
5 & 6. Postfix and Prefix Increment/Decrement
These operators have slightly different precedence and behavior.
let x = 5;
let y = x++;
console.log(x, y); // 6, 5
let a = 5;
let b = ++a;
console.log(a, b); // 6, 6
Postfix (x++
) increments after the value is used, while prefix (++a
) increments before the value is used.
7. Unary Operators
Unary operators act on a single operand. They have higher precedence than binary operators.
let x = 5;
console.log(-x); // -5
console.log(typeof x); // "number"
let y = "123";
console.log(+y); // 123 (converts string to number)
let z = true;
console.log(!z); // false
The unary plus (+
) can be used to convert a string to a number, while the logical NOT (!
) operator inverts a boolean value.
8. Exponentiation
The exponentiation operator (**
) was introduced in ES2016. It has higher precedence than multiplication and division.
console.log(2 ** 3); // 8
console.log(2 ** 3 * 2); // 16 (not 64)
console.log(2 ** (3 * 2)); // 64
In the second example, 2 ** 3
is evaluated first (8), then multiplied by 2. To change this, we use parentheses in the third example.
9. Multiplication, Division, and Remainder
These operators have equal precedence and are evaluated left-to-right.
console.log(10 * 5 / 2); // 25
console.log(10 / 5 * 2); // 4
console.log(10 % 3 * 2); // 2 (10 % 3 = 1, then 1 * 2 = 2)
10. Addition and Subtraction
Addition and subtraction have equal precedence and are evaluated left-to-right.
console.log(5 + 3 - 2); // 6
console.log(5 - 3 + 2); // 4
🔍 Interesting Fact: In JavaScript, the +
operator is also used for string concatenation. When used with a string and a number, it converts the number to a string:
console.log("5" + 3); // "53"
console.log(5 + "3"); // "53"
11. Bitwise Shift
Bitwise shift operators move the bits of the first operand left or right.
console.log(8 << 2); // 32 (8 * 2^2)
console.log(8 >> 2); // 2 (8 / 2^2)
console.log(-8 >>> 2); // 1073741822 (fills with zeros)
These operators are less commonly used but can be very useful in certain scenarios, especially when working with binary data or optimizing performance.
12. Relational Operators
Relational operators compare two values and return a boolean.
console.log(5 > 3); // true
console.log(5 <= 5); // true
console.log("apple" in {apple: 5, banana: 3}); // true
console.log([] instanceof Array); // true
The in
operator checks if a property exists in an object, while instanceof
checks if an object is an instance of a particular type.
13. Equality Operators
Equality operators check if two values are equal. The strict equality operator (===
) also checks for type equality.
console.log(5 == "5"); // true
console.log(5 === "5"); // false
console.log(5 != "6"); // true
console.log(5 !== 5); // false
🚨 Best Practice: Always use strict equality (===
) and strict inequality (!==
) to avoid unexpected type coercion.
14, 15, 16. Bitwise AND, XOR, and OR
These bitwise operators perform operations on the binary representations of numbers.
console.log(5 & 3); // 1 (101 & 011 = 001)
console.log(5 ^ 3); // 6 (101 ^ 011 = 110)
console.log(5 | 3); // 7 (101 | 011 = 111)
While not as common in everyday JavaScript, these operators can be crucial for certain algorithms and low-level operations.
17 & 18. Logical AND and OR
Logical AND (&&
) and OR (||
) operators are used for boolean logic, but they have some unique behaviors in JavaScript.
console.log(true && false); // false
console.log(true || false); // true
// Short-circuit evaluation
console.log(false && someUndefinedVariable); // false (someUndefinedVariable is not evaluated)
console.log(true || someUndefinedVariable); // true (someUndefinedVariable is not evaluated)
// Truthy and falsy values
console.log("hello" && 5); // 5
console.log(0 || "fallback"); // "fallback"
These operators exhibit short-circuit behavior, which can be used for conditional execution or providing default values.
19. Conditional (Ternary) Operator
The ternary operator is a shorthand way of writing an if-else statement.
let age = 20;
let status = age >= 18 ? "adult" : "minor";
console.log(status); // "adult"
// Nested ternary (use with caution)
let greeting = age < 18 ? "Hey kiddo!" : age < 65 ? "Hello!" : "Hello, respected elder!";
console.log(greeting); // "Hello!"
While powerful, nested ternary operators can quickly become hard to read. Use them judiciously.
20. Assignment Operators
Assignment operators have very low precedence. They're typically evaluated last in an expression.
let x = 5;
x += 3; // Equivalent to x = x + 3
console.log(x); // 8
let y = 10;
y *= 2 + 3; // Equivalent to y = y * (2 + 3), not (y * 2) + 3
console.log(y); // 50
🔑 Key Point: The right side of the assignment is always evaluated before the assignment takes place.
21. Comma Operator
The comma operator has the lowest precedence. It evaluates multiple expressions and returns the value of the last one.
let x = (2, 3, 4);
console.log(x); // 4
let y = 1, z = 2;
console.log(y, z); // 1 2
The comma operator is rarely used, but it can be handy in certain situations, like in for
loop declarations.
Practical Examples
Now that we've covered all the precedence rules, let's look at some practical examples that combine multiple operators:
Example 1: Complex Arithmetic
let result = 2 + 3 * 4 ** 2 - 6 / 2;
console.log(result); // 48
Here's how this is evaluated:
4 ** 2
= 16 (exponentiation)3 * 16
= 48 (multiplication)6 / 2
= 3 (division)2 + 48 - 3
= 47 (addition and subtraction from left to right)
Example 2: Logical Operations with Comparison
let x = 5, y = 10, z = 15;
let result = x < y && y < z || x > z;
console.log(result); // true
Evaluation steps:
x < y
istrue
y < z
istrue
true && true
istrue
x > z
isfalse
true || false
istrue
Example 3: Function Calls and Member Access
let obj = {
value: 5,
getValue: function() { return this.value; }
};
function double(x) { return x * 2; }
let result = double(obj.getValue()) + obj.value;
console.log(result); // 15
Evaluation steps:
obj.getValue
accesses the method (member access)obj.getValue()
calls the method (function call)double(...)
calls thedouble
function with the result ofobj.getValue()
obj.value
accesses thevalue
property- The results are added together
Example 4: Unary and Binary Operators
let x = 5;
let y = 3;
let result = -x++ + ++y * 2;
console.log(result, x, y); // -2, 6, 4
Evaluation steps:
x++
is evaluated (postfix), butx
is still 5 for this expression-x
negates 5 to -5++y
incrementsy
to 4 (prefix)4 * 2
= 8-5 + 8
= 3- After the expression,
x
is incremented to 6
Conclusion
Understanding operator precedence is crucial for writing correct and predictable JavaScript code. While it's not necessary to memorize the entire precedence table, being familiar with the general rules can help you write more efficient code and avoid common pitfalls.
Remember these key points:
- Parentheses can always be used to explicitly define the order of operations.
- Unary operators generally have higher precedence than binary operators.
- Assignment has very low precedence and is usually evaluated last.
- When in doubt, use parentheses to make your intentions clear.
By mastering operator precedence, you'll be able to write more complex expressions with confidence, debug issues more effectively, and create more elegant solutions to programming problems. Keep practicing with different combinations of operators, and soon you'll develop an intuitive understanding of how JavaScript evaluates expressions.
- What is Operator Precedence?
- The Precedence Table
- 1. Grouping ( )
- 2. Member Access . []
- 3. Function Call ()
- 4. new (with argument list)
- 5 & 6. Postfix and Prefix Increment/Decrement
- 7. Unary Operators
- 8. Exponentiation
- 9. Multiplication, Division, and Remainder
- 10. Addition and Subtraction
- 11. Bitwise Shift
- 12. Relational Operators
- 13. Equality Operators
- 14, 15, 16. Bitwise AND, XOR, and OR
- 17 & 18. Logical AND and OR
- 19. Conditional (Ternary) Operator
- 20. Assignment Operators
- 21. Comma Operator
- Practical Examples
- Conclusion