JavaScript Operators and Expressions
Learning Objectives
- Master the concepts in this lesson
- Apply knowledge through practice
- Build practical skills
- Prepare for next topics
Understanding JavaScript Operators and Expressions
Operators are the building blocks of JavaScript that allow us to perform operations on data. Think of operators as specialized tools in a toolbox—each designed for a specific purpose, whether it's adding numbers together, comparing values, or combining logical conditions. If variables are the nouns in the language of JavaScript, operators are the verbs that make things happen.
graph TD
A[JavaScript Operators] --> B[Arithmetic Operators]
A --> C[Assignment Operators]
A --> D[Comparison Operators]
A --> E[Logical Operators]
A --> F[String Operators]
A --> G[Bitwise Operators]
A --> H[Ternary Operator]
A --> I[Type Operators]
B --> B1["+, -, *, /, %, **, ++, --"]
C --> C1["=, +=, -=, *=, /=, %="]
D --> D1["==, ===, !=, !==, >, <, >=, <="]
E --> E1["&&, ||, !"]
F --> F1["+"]
G --> G1["&, |, ~, ^, <<, >>, >>>"]
H --> H1["? :"]
I --> I1["typeof, instanceof"]
An expression in JavaScript is any valid combination of operators, values, and variables that produces a result. You can think of expressions as mathematical formulas or sentences that JavaScript can understand and evaluate. Simple expressions might be a single value like 42 or a variable like firstName, while complex expressions might combine multiple operators and values to calculate a final result.
Arithmetic Operators: Calculating with Numbers
Arithmetic operators allow you to perform mathematical operations. They're the digital equivalent of the basic operations you learned in elementary school mathematics, plus a few additional tools.
The Core Arithmetic Operators
// Addition (+)
let sum = 5 + 3; // 8
let combinedName = "John" + " " + "Doe"; // "John Doe" (string concatenation)
// Subtraction (-)
let difference = 10 - 4; // 6
let negation = -difference; // -6 (unary negation)
// Multiplication (*)
let product = 3 * 4; // 12
let doubledPrice = 19.99 * 2; // 39.98
// Division (/)
let quotient = 20 / 5; // 4
let decimalResult = 10 / 3; // 3.3333333333333335
// Modulus (%) - returns the remainder after division
let remainder = 7 % 3; // 1 (7 divided by 3 is 2 with remainder 1)
let isEven = (number % 2 === 0); // Check if a number is even
// Exponentiation (**) - power/exponential calculation
let power = 2 ** 3; // 8 (2³ = 2*2*2 = 8)
let squared = 5 ** 2; // 25 (5² = 5*5 = 25)
Increment and Decrement Operators
These special operators add or subtract 1 from a variable. They have two forms: prefix (++x, --x) and postfix (x++, x--), with an important difference in behavior.
// Increment (++)
let count = 5;
count++; // Postfix: use the current value, then increment (now count is 6)
let score = 10;
++score; // Prefix: increment first, then use the value (now score is 11)
// The difference becomes important in expressions
let a = 5;
let b = a++; // b gets the original value (5), then a is incremented to 6
let c = 5;
let d = ++c; // c is incremented to 6 first, then d gets the new value (6)
// Decrement (--)
let lives = 3;
lives--; // Postfix: use the current value, then decrement (now lives is 2)
let level = 7;
--level; // Prefix: decrement first, then use the value (now level is 6)
Real-World Example: Shopping Cart Calculator
function calculateCartTotal(items, promoCode) {
// Initialize variables
let subtotal = 0;
let discount = 0;
const TAX_RATE = 0.07; // 7% sales tax
// Calculate subtotal by adding all item prices
for (let i = 0; i < items.length; i++) {
subtotal += items[i].price * items[i].quantity;
}
// Apply promotions
if (promoCode === "SAVE10") {
discount = subtotal * 0.1; // 10% discount
} else if (promoCode === "SAVE20" && subtotal >= 100) {
discount = subtotal * 0.2; // 20% discount for orders over $100
}
// Calculate final amounts
const discountedSubtotal = subtotal - discount;
const tax = discountedSubtotal * TAX_RATE;
const total = discountedSubtotal + tax;
return {
subtotal: subtotal.toFixed(2),
discount: discount.toFixed(2),
tax: tax.toFixed(2),
total: total.toFixed(2)
};
}
Practical Applications of the Modulus Operator
- Checking odd/even numbers:
number % 2 === 0is true for even numbers - Cycling through arrays:
index = (index + 1) % array.lengthloops back to 0 after reaching the end - Time formatting:
hours % 12converts 24-hour format to 12-hour format - Alternating patterns:
row % 2 === 0can create zebra striping in tables - Distributing items:
item % numberOfBucketsassigns items to buckets evenly
Assignment Operators: Storing Values in Variables
Assignment operators store values in variables. They combine assignment with arithmetic operations for more concise code. Think of these as a shorthand way of saying "update this variable using this operation."
Basic Assignment Operator
// The equals sign (=) is the basic assignment operator
let message = "Hello, world!";
let count = 42;
let isActive = true;
Compound Assignment Operators
// Addition assignment (+=)
let score = 10;
score += 5; // Equivalent to: score = score + 5; (score is now 15)
// Subtraction assignment (-=)
let lives = 3;
lives -= 1; // Equivalent to: lives = lives - 1; (lives is now 2)
// Multiplication assignment (*=)
let factor = 2;
factor *= 4; // Equivalent to: factor = factor * 4; (factor is now 8)
// Division assignment (/=)
let ratio = 100;
ratio /= 5; // Equivalent to: ratio = ratio / 5; (ratio is now 20)
// Remainder/modulus assignment (%=)
let remainder = 17;
remainder %= 5; // Equivalent to: remainder = remainder % 5; (remainder is now 2)
// Exponentiation assignment (**=)
let power = 2;
power **= 3; // Equivalent to: power = power ** 3; (power is now 8)
Real-World Example: Game Score Tracking
function updateGameScore(action, points) {
// Initialize or retrieve game state
let gameState = {
score: 0,
lives: 3,
level: 1,
multiplier: 1,
bonusPoints: 0
};
// Update game state based on action
switch (action) {
case "collectCoin":
gameState.score += 10 * gameState.multiplier;
break;
case "defeatEnemy":
gameState.score += points * gameState.multiplier;
gameState.bonusPoints += Math.floor(points / 10);
break;
case "takeDamage":
gameState.lives -= 1;
gameState.multiplier = 1; // Reset multiplier when damaged
break;
case "collectPowerup":
gameState.multiplier *= 2; // Double the score multiplier
break;
case "completeLevel":
gameState.level += 1;
gameState.score += 100 * gameState.level;
// Award extra life every 3 levels
if (gameState.level % 3 === 0) {
gameState.lives += 1;
}
break;
}
// Apply bonus points if enough accumulated
if (gameState.bonusPoints >= 100) {
gameState.score += 500;
gameState.bonusPoints %= 100; // Keep only the remainder
}
return gameState;
}
Chaining Assignments
You can assign the same value to multiple variables by chaining assignments:
// All variables get the value 5
let a, b, c;
a = b = c = 5;
// The chain evaluates from right to left
// First, c = 5 is evaluated (which assigns 5 to c and returns 5)
// Then b = 5 is evaluated (which assigns 5 to b and returns 5)
// Finally, a = 5 is evaluated (which assigns 5 to a)
Comparison Operators: Evaluating Conditions
Comparison operators compare two values and return a boolean result (true or false). These operators are essential for creating conditions in your programs, allowing code to make decisions based on data.
sequenceDiagram
participant V as Values
participant C as Comparison Operator
participant R as Result (Boolean)
Note over V,R: The Comparison Process
V->>C: Provide values for comparison
C->>C: Evaluate the relationship
C->>R: Return true or false
Note over R: Decision making happens based on this result
Basic Comparison Operators
// Equal to (==) - Compares values, performs type conversion
console.log(5 == 5); // true
console.log("5" == 5); // true (string "5" is converted to number 5)
console.log(0 == false); // true (false is converted to 0)
// Strict equal to (===) - Compares values AND types, no conversion
console.log(5 === 5); // true
console.log("5" === 5); // false (different types: string vs number)
console.log(0 === false); // false (different types: number vs boolean)
// Not equal to (!=) - Opposite of ==, performs type conversion
console.log(5 != 8); // true (5 is not equal to 8)
console.log("5" != 5); // false (after conversion, they're considered equal)
// Strict not equal to (!==) - Opposite of ===, checks values AND types
console.log(5 !== 8); // true (5 is not equal to 8)
console.log("5" !== 5); // true (different types: string vs number)
// Greater than (>)
console.log(10 > 5); // true (10 is greater than 5)
console.log(5 > 5); // false (5 is not greater than 5)
// Less than (<)
console.log(5 < 10); // true (5 is less than 10)
console.log(5 < 5); // false (5 is not less than 5)
// Greater than or equal to (>=)
console.log(10 >= 5); // true (10 is greater than 5)
console.log(5 >= 5); // true (5 is equal to 5)
// Less than or equal to (<=)
console.log(5 <= 10); // true (5 is less than 10)
console.log(5 <= 5); // true (5 is equal to 5)
When to Use == vs ===
In modern JavaScript, it's generally recommended to use strict equality (===) and strict inequality (!==) rather than loose equality (==) and loose inequality (!=).
Why Use Strict Equality?
- Prevents unexpected type conversions
- Makes your code more predictable
- Helps catch potential bugs
- Improves performance (no type conversion needed)
Loose Equality Gotchas
// Unexpected equivalences with ==
"" == 0 // true
0 == false // true
null == undefined // true
" \t\r\n" == 0 // true (blank string converts to 0)
// These differences are clear with ===
"" === 0 // false
0 === false // false
null === undefined // false
" \t\r\n" === 0 // false
Real-World Example: User Authentication
function validateCredentials(username, password, userDatabase) {
// Check if username exists
if (username === "" || password === "") {
return {
success: false,
message: "Username and password cannot be empty"
};
}
// Find user in database
const user = userDatabase.find(user => user.username === username);
// Check if user exists
if (user === undefined) {
return {
success: false,
message: "User not found"
};
}
// Check password
if (user.password !== password) {
return {
success: false,
message: "Incorrect password"
};
}
// Check account status
if (user.accountLocked === true) {
return {
success: false,
message: "Account is locked. Please contact support."
};
}
// Authentication successful
return {
success: true,
message: "Login successful",
userData: {
id: user.id,
username: user.username,
fullName: user.fullName,
role: user.role,
lastLogin: new Date()
}
};
}
Special Comparison Cases
// Comparing strings - compares character by character using alphabetical order
console.log("apple" < "banana"); // true (alphabetically, 'a' comes before 'b')
console.log("apple" < "Apple"); // false (uppercase comes before lowercase in ASCII)
// Comparing different types
console.log("2" > 1); // true ("2" is converted to number 2)
console.log(true > 0); // true (true is converted to number 1)
// Comparing null and undefined
console.log(null == undefined); // true (loose equality considers them equal)
console.log(null === undefined); // false (strict equality sees different types)
// Special values
console.log(NaN == NaN); // false (NaN is not equal to anything, even itself!)
console.log(NaN === NaN); // false
console.log(isNaN(NaN)); // true (use isNaN function to check for NaN)
Logical Operators: Combining Conditions
Logical operators allow you to combine multiple conditions. Think of them as the glue that connects various tests together, letting you create complex decision-making logic based on multiple factors.
Logical AND (&&)
Returns true only if both operands are true.
// Logical AND truth table
console.log(true && true); // true
console.log(true && false); // false
console.log(false && true); // false
console.log(false && false); // false
// Practical examples
let age = 25;
let hasLicense = true;
// Both conditions must be true to drive
let canDrive = age >= 16 && hasLicense; // true
// Check for valid username (between 3 and 20 characters)
function isValidUsername(username) {
return username.length >= 3 && username.length <= 20;
}
Logical OR (||)
Returns true if at least one operand is true.
// Logical OR truth table
console.log(true || true); // true
console.log(true || false); // true
console.log(false || true); // true
console.log(false || false); // false
// Practical examples
let isWeekend = false;
let isHoliday = true;
// Either condition makes it a day off
let isDayOff = isWeekend || isHoliday; // true
// Use default value if input is not provided
function greet(name) {
// If name is falsy (empty string, null, undefined), use "Guest"
let userName = name || "Guest";
return "Hello, " + userName + "!";
}
console.log(greet("John")); // "Hello, John!"
console.log(greet("")); // "Hello, Guest!"
Logical NOT (!)
Returns the opposite boolean value of the operand.
// Logical NOT examples
console.log(!true); // false
console.log(!false); // true
console.log(!!true); // true (double negation, back to original)
// Practical examples
let isLoggedIn = false;
let showLoginButton = !isLoggedIn; // true (show login button if not logged in)
// Convert value to boolean (!! trick)
let username = "John";
let hasUsername = !!username; // true (username is not empty)
let emptyString = "";
let hasValue = !!emptyString; // false (empty string is falsy)
Short-Circuit Evaluation
Logical operators in JavaScript use "short-circuit" evaluation, which means they stop evaluating as soon as the result is determined.
// AND (&&) short-circuit: if the first operand is false,
// the second isn't evaluated because the result must be false
function getUserData() {
console.log("Getting user data...");
return { name: "John" };
}
let isLoggedIn = false;
let userData = isLoggedIn && getUserData();
// getUserData() is never called because isLoggedIn is false
// OR (||) short-circuit: if the first operand is true,
// the second isn't evaluated because the result must be true
function getDefaultSettings() {
console.log("Loading default settings...");
return { theme: "light" };
}
let userSettings = { theme: "dark" };
let settings = userSettings || getDefaultSettings();
// getDefaultSettings() is never called because userSettings is truthy
Nullish Coalescing Operator (??)
This modern JavaScript operator is similar to || but works specifically with null or undefined values, not all falsy values.
// Nullish coalescing operator (??)
// Returns right-hand value only if left-hand is null or undefined
// Difference between || and ??
let count = 0;
let defaultCount = 5;
// OR treats 0 as falsy, so it uses the default
let countWithOr = count || defaultCount; // 5
// Nullish coalescing only checks for null/undefined, so it keeps 0
let countWithNullish = count ?? defaultCount; // 0
// More examples
let username = "";
let defaultUsername = "Guest";
let userDisplayName = username || defaultUsername; // "Guest" (empty string is falsy)
let actualUsername = username ?? defaultUsername; // "" (empty string is not null/undefined)
Real-World Example: Form Validation
function validateRegistrationForm(formData) {
const errors = {};
// Username validation
if (!formData.username) {
errors.username = "Username is required";
} else if (formData.username.length < 3 || formData.username.length > 20) {
errors.username = "Username must be between 3 and 20 characters";
}
// Email validation
const emailPattern = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
if (!formData.email) {
errors.email = "Email is required";
} else if (!emailPattern.test(formData.email)) {
errors.email = "Please enter a valid email address";
}
// Password validation
if (!formData.password) {
errors.password = "Password is required";
} else {
const hasMinLength = formData.password.length >= 8;
const hasUppercase = /[A-Z]/.test(formData.password);
const hasLowercase = /[a-z]/.test(formData.password);
const hasNumber = /[0-9]/.test(formData.password);
const hasSpecialChar = /[!@#$%^&*]/.test(formData.password);
if (!(hasMinLength && hasUppercase && hasLowercase && hasNumber)) {
errors.password = "Password must be at least 8 characters and include uppercase, lowercase, and numbers";
}
// Suggest stronger password if no special character
if (!hasSpecialChar) {
errors.passwordSuggestion = "Consider adding special characters for a stronger password";
}
}
// Password confirmation
if (formData.password && formData.confirmPassword &&
formData.password !== formData.confirmPassword) {
errors.confirmPassword = "Passwords do not match";
}
// Age verification
const age = Number(formData.age);
if (!formData.age) {
errors.age = "Age is required";
} else if (isNaN(age) || age < 13) {
errors.age = "You must be at least 13 years old to register";
}
// Terms agreement
if (!formData.agreeToTerms) {
errors.agreeToTerms = "You must agree to the terms and conditions";
}
// Return validation result
return {
isValid: Object.keys(errors).length === 0,
errors: errors
};
}
String Operators: Working with Text
JavaScript uses the plus operator (+) for string concatenation, allowing you to combine strings together. In modern JavaScript, template literals provide a more elegant way to construct strings.
String Concatenation with +
// Basic string concatenation
let firstName = "John";
let lastName = "Doe";
let fullName = firstName + " " + lastName; // "John Doe"
// Mixing strings and numbers
let age = 30;
let message = "I am " + age + " years old"; // "I am 30 years old"
// Be careful with order of operations
let result = "The sum is: " + 5 + 10; // "The sum is: 510" (not 15!)
let correctResult = "The sum is: " + (5 + 10); // "The sum is: 15"
// Compound assignment with strings
let greeting = "Hello";
greeting += " World"; // greeting is now "Hello World"
Template Literals (ES6+)
// Template literals use backticks (`) and ${} for expressions
let name = "Jane";
let greeting = `Hello, ${name}!`; // "Hello, Jane!"
// Multi-line strings
let multiLine = `This is a string
that spans multiple
lines without needing \n`;
// Expressions in template literals
let a = 5;
let b = 10;
let expression = `The sum of ${a} and ${b} is ${a + b}`; // "The sum of 5 and 10 is 15"
// Calling functions in template literals
function capitalize(str) {
return str.charAt(0).toUpperCase() + str.slice(1);
}
let pet = "dog";
let sentence = `I have a ${capitalize(pet)}`; // "I have a Dog"
Real-World Example: Dynamic HTML Generation
function generateProductCard(product) {
// Using template literals to create HTML structure
const cardHTML = `
<div class="product-card" id="product-${product.id}">
<div class="product-image">
<img src="${product.imageUrl}" alt="${product.name}"
${product.stock === 0 ? 'class="out-of-stock"' : ''}>
${product.sale ? '<span class="sale-badge">SALE</span>' : ''}
</div>
<div class="product-info">
<h3 class="product-name">${product.name}</h3>
<p class="product-description">${product.description}</p>
<div class="product-price">
${product.sale
? `<span class="original-price">$${product.originalPrice.toFixed(2)}</span>
<span class="sale-price">$${product.salePrice.toFixed(2)}</span>`
: `<span class="regular-price">$${product.price.toFixed(2)}</span>`
}
</div>
<div class="product-actions">
<button class="add-to-cart"
${product.stock === 0 ? 'disabled' : ''}
data-product-id="${product.id}">
${product.stock === 0 ? 'Out of Stock' : 'Add to Cart'}
</button>
<button class="add-to-wishlist" data-product-id="${product.id}">
♥
</button>
</div>
</div>
</div>
`;
return cardHTML;
}
// Example usage
const product = {
id: 123,
name: "Wireless Headphones",
description: "Premium noise-cancelling bluetooth headphones with 30-hour battery life.",
price: 149.99,
sale: true,
originalPrice: 149.99,
salePrice: 119.99,
imageUrl: "/images/headphones-blue.jpg",
stock: 5
};
// Insert the product card into the page
document.querySelector(".product-container").innerHTML += generateProductCard(product);
Other Important Operators
Ternary Operator (Conditional Operator)
A shorthand for if-else conditions. Think of it as a compact decision-making tool.
// Syntax: condition ? valueIfTrue : valueIfFalse
// Simple example
let age = 17;
let canVote = age >= 18 ? "Yes" : "No"; // "No"
// Nested ternary (use with caution - can be hard to read)
let timeOfDay = 14; // 24-hour format
let greeting = timeOfDay < 12 ? "Good morning" :
timeOfDay < 18 ? "Good afternoon" :
"Good evening"; // "Good afternoon"
// Ternary in template literals
let status = "active";
let statusDisplay = `User status: ${status === "active" ? "✓ Active" : "✗ Inactive"}`;
// Alternative to if/else with side effects
function displayWelcome(user) {
const welcomeEl = document.getElementById("welcome");
user.isLoggedIn
? welcomeEl.textContent = `Welcome back, ${user.name}!`
: welcomeEl.textContent = "Please log in";
}
Type Operators
These operators help you work with JavaScript's dynamic typing system.
// typeof operator - returns a string indicating the type
console.log(typeof 42); // "number"
console.log(typeof "hello"); // "string"
console.log(typeof true); // "boolean"
console.log(typeof undefined); // "undefined"
console.log(typeof null); // "object" (historical JavaScript quirk/bug)
console.log(typeof {}); // "object"
console.log(typeof []); // "object" (arrays are objects in JavaScript)
console.log(typeof function(){}); // "function"
// instanceof operator - checks if an object is an instance of a class
let arr = [1, 2, 3];
let obj = { prop: "value" };
let date = new Date();
console.log(arr instanceof Array); // true
console.log(obj instanceof Object); // true
console.log(date instanceof Date); // true
console.log(arr instanceof Object); // true (arrays inherit from Object)
console.log(date instanceof Array); // false
Optional Chaining Operator (?.)
Modern JavaScript operator that safely accesses nested properties without causing errors if a property doesn't exist.
// Problem without optional chaining
let user = {}; // User with no address property
// This would cause an error:
// let city = user.address.city; // TypeError: Cannot read property 'city' of undefined
// Traditional safe approach
let city;
if (user && user.address && user.address.city) {
city = user.address.city;
} else {
city = "Unknown";
}
// With optional chaining
let cityWithChaining = user?.address?.city ?? "Unknown"; // "Unknown"
// It also works with methods
let userFunction = user?.getFullName?.() ?? "No name available";
// And with array elements
let firstItem = someArray?.[0] ?? "No items";
Bitwise Operators
These operate on the binary (bit) level of numbers. They're less commonly used in everyday JavaScript but are powerful for certain tasks.
// Bitwise AND (&)
console.log(5 & 3); // 1 (0101 & 0011 = 0001)
// Bitwise OR (|)
console.log(5 | 3); // 7 (0101 | 0011 = 0111)
// Bitwise XOR (^)
console.log(5 ^ 3); // 6 (0101 ^ 0011 = 0110)
// Bitwise NOT (~)
console.log(~5); // -6 (inverts all bits and adds 1 to the result)
// Left shift (<<)
console.log(5 << 1); // 10 (shift all bits left by 1 = multiplication by 2)
// Right shift (>>)
console.log(5 >> 1); // 2 (shift all bits right by 1 = division by 2)
Practical Uses for Bitwise Operators
- Permissions and flags: Using bits to represent a set of boolean options
- Color manipulation: Working with RGB values
- Performance optimization: Fast multiplying/dividing by powers of 2
- Low-level algorithms: Cryptography, compression, etc.
// Example: User permissions using bitwise flags
const READ = 1; // 001
const WRITE = 2; // 010
const DELETE = 4; // 100
// Combine permissions
let userPermissions = READ | WRITE; // 011 (3)
// Check for a permission
function hasPermission(perms, perm) {
return (perms & perm) === perm;
}
console.log(hasPermission(userPermissions, READ)); // true
console.log(hasPermission(userPermissions, WRITE)); // true
console.log(hasPermission(userPermissions, DELETE)); // false
// Add a permission
userPermissions |= DELETE; // 111 (7)
// Remove a permission
userPermissions &= ~WRITE; // 101 (5)
Operator Precedence and Associativity
Just like in mathematics, JavaScript operators follow specific precedence rules that determine the order of evaluation. Think of this as the "PEMDAS" (Parentheses, Exponents, Multiplication/Division, Addition/Subtraction) of JavaScript—but with many more operators.
graph TD
A[Operator Precedence] --> B[Highest Priority]
B --> B1["() - Grouping"]
B1 --> B2[". [] ?. - Member Access"]
B2 --> B3["new - Constructor Call"]
B3 --> C[Middle Priority]
C --> C1["! ~ - Unary Operators"]
C1 --> C2["* / % - Multiplicative"]
C2 --> C3["+ - - Additive"]
C3 --> C4[">= > < <= - Comparison"]
C4 --> C5["=== == !== != - Equality"]
C5 --> D[Lower Priority]
D --> D1["&& - Logical AND"]
D1 --> D2["|| - Logical OR"]
D2 --> D3["?: - Conditional (Ternary)"]
D3 --> D4["= += -= *= /= - Assignment"]
D4 --> E[Lowest Priority]
Examples of Operator Precedence
// Arithmetic operators have higher precedence than comparison operators
console.log(5 + 3 > 7); // true (5 + 3 is 8, then 8 > 7 is true)
// Comparison operators have higher precedence than logical operators
console.log(5 > 3 && 10 > 5); // true (5 > 3 is true, 10 > 5 is true, true && true is true)
// Multiple operators with different precedence
console.log(3 + 4 * 5); // 23 (not 35, multiplication happens first)
// Using parentheses to control order
console.log((3 + 4) * 5); // 35 (parentheses force addition to happen first)
Associativity: Left-to-Right vs. Right-to-Left
Associativity determines the order of operations when operators have the same precedence.
// Left-to-right associativity (most operators)
console.log(10 / 5 / 2); // 1 (not 1, evaluated as (10 / 5) / 2)
// Right-to-left associativity (assignment, unary, etc.)
let a, b, c;
a = b = c = 5; // evaluated as a = (b = (c = 5))
// Unary operators are right-to-left
console.log(!!false); // false (evaluated as !(!(false)))
Using Parentheses for Clarity
// Complex expressions can be hard to read without parentheses
let complex = a || b && c || d;
// With parentheses it's clearer how it will be evaluated
let clear = a || (b && c) || d;
// Even when not strictly needed, parentheses can improve readability
let age = 25;
let hasSubscription = true;
let isPremiumContent = true;
// Without parentheses
let canAccess = age >= 18 && hasSubscription || isPremiumContent;
// With parentheses - much clearer intent
let canAccessClear = (age >= 18 && hasSubscription) || isPremiumContent;
Best Practices for Working with Operators
- Use parentheses liberally to make your intent clear and avoid relying on precedence rules
- Break complex expressions into multiple lines or intermediate variables
- Use strict equality (===, !==) rather than loose equality (==, !=)
- Be careful with implicit type conversion in operations like addition with different types
- Comment complex operations to explain the intent behind them
Improving Complex Expressions
// Before: Complex and hard to understand
let eligibility = age >= 18 && income > 30000 && creditScore >= 700 || hasSpecialApproval && !isBlacklisted;
// After: More readable with intermediate variables
let meetsAgeRequirement = age >= 18;
let meetsIncomeRequirement = income > 30000;
let meetsCredit = creditScore >= 700;
let meetsStandardCriteria = meetsAgeRequirement && meetsIncomeRequirement && meetsCredit;
let meetsExceptionCriteria = hasSpecialApproval && !isBlacklisted;
let eligibility = meetsStandardCriteria || meetsExceptionCriteria;
JavaScript Expressions in Depth
An expression is any valid unit of code that resolves to a value. Expressions are the building blocks of JavaScript statements. Think of expressions as phrases that can be evaluated to produce a result.
Types of Expressions
Arithmetic Expressions
// Simple arithmetic
let result = 10 + 5; // 15
// Complex calculation
let area = Math.PI * radius * radius;
// Function call with arithmetic
let rounded = Math.round(3.14159 * 1000) / 1000; // 3.142
String Expressions
// String concatenation
let greeting = "Hello, " + firstName + " " + lastName;
// Template literal
let welcome = `Welcome to ${siteName}, ${username}!`;
// Multi-part string operation
let truncated = text.substring(0, 100) + (text.length > 100 ? "..." : "");
Logical Expressions
// Combining conditions
let isEligible = age >= 18 && hasValidID;
// Complex business logic
let canProceed = (isAdmin || hasPermission) && isResourceAvailable && !isMaintenance;
Assignment Expressions
// Assignment as an expression (returns the assigned value)
let a, b;
console.log(a = b = 5); // 5
// Chained assignment in context
function initializeCounters() {
let i, j, k;
return (i = j = k = 0); // Returns 0 and initializes all variables
}
Function Call Expressions
// Basic function call
let result = calculateTotal(subtotal, tax);
// Nested function calls
let formattedResult = formatCurrency(calculateTotal(subtotal, getTaxRate(location)));
Where Expressions Can Be Used
- Variable assignments:
let x = 5 * 10; - Function arguments:
console.log(a + b); - Return statements:
return isValid ? data : null; - Conditions:
if (score >= passingGrade) { ... } - Array elements:
[1, 2, 3+4, x*2] - Object properties:
{ id: generateId(), name: firstName + " " + lastName } - Template literals:
`You scored ${score}/${total} (${(score/total*100).toFixed(1)}%)`
Real-World Example: Dynamic Report Generator
function generateSalesReport(salesData, timeFrame, options = {}) {
// Set defaults using expressions
const currency = options.currency || "USD";
const target = options.targetAmount || 10000;
const includeDetails = options.showDetails !== undefined ? options.showDetails : true;
// Calculate metrics using expressions
const totalSales = salesData.reduce((sum, sale) => sum + sale.amount, 0);
const highestSale = Math.max(...salesData.map(sale => sale.amount));
const averageSale = totalSales / (salesData.length || 1); // Avoid division by zero
const targetPercentage = (totalSales / target) * 100;
const targetMet = totalSales >= target;
// Generate summary using expressions in template literals
const summary = {
title: `Sales Report: ${timeFrame}`,
overview: `Total sales: ${formatCurrency(totalSales, currency)}
(${targetPercentage.toFixed(1)}% of ${formatCurrency(target, currency)} target)`,
status: targetMet ? "Target achieved! 🎉" : `${formatCurrency(target - totalSales, currency)} below target`,
performance: totalSales > target * 1.2 ? "Exceptional" :
totalSales >= target ? "On Target" :
totalSales >= target * 0.8 ? "Below Target" : "Needs Attention",
topSale: formatCurrency(highestSale, currency),
averageSale: formatCurrency(averageSale, currency)
};
// Conditionally include detailed breakdown based on an expression
if (includeDetails && salesData.length > 0) {
summary.details = salesData.map(sale => ({
id: sale.id,
date: formatDate(sale.date),
amount: formatCurrency(sale.amount, currency),
customer: sale.customerName || "Anonymous",
product: sale.productName,
profit: formatCurrency(sale.amount - (sale.cost || 0), currency)
}));
// Additional analytics using expressions
summary.analytics = {
salesByDay: groupSalesByDay(salesData),
topProducts: getTopProducts(salesData, 5),
growthRate: calculateGrowthRate(salesData, timeFrame),
forecastNextPeriod: forecastSales(salesData, timeFrame)
};
}
return summary;
}
// Helper function with expression
function formatCurrency(amount, currency = "USD") {
return new Intl.NumberFormat('en-US', {
style: 'currency',
currency: currency
}).format(amount);
}
Expressions with Side Effects
Some expressions not only produce a value but also change something (a side effect).
// Assignment operators have side effects (change variable values)
let counter = 0;
counter++; // Side effect: counter is now 1, expression value is 0
++counter; // Side effect: counter is now 2, expression value is 2
// Function calls can have side effects
document.getElementById('message').textContent = 'Hello'; // DOM modification
console.log('Testing'); // Console output
localStorage.setItem('lastVisit', new Date().toString()); // Storage modification
// Method calls can modify their objects
let arr = [1, 2, 3];
arr.push(4); // Side effect: array is now [1,2,3,4], expression value is 4
Avoiding Unintended Side Effects
// Potential bug with side effects in conditions
let count = 1;
if (count++ > 1) { // count is incremented, but the comparison uses the original value
console.log('Count is greater than 1'); // Not executed
}
console.log(count); // 2
// Better approach
let count = 1;
if (count > 1) {
console.log('Count is greater than 1');
}
count++; // Clearer intent
// Another example: unintended side effect with &&
function saveData() {
console.log('Data saved');
return true;
}
let isValid = false;
let result = isValid && saveData(); // saveData() never runs because of short-circuiting
Advanced Practical Examples with Operators and Expressions
Data Transformation
// Transform raw user data into a formatted display object
function formatUserData(rawUser) {
return {
displayName: rawUser.firstName && rawUser.lastName
? `${rawUser.firstName} ${rawUser.lastName}`
: rawUser.username || "Anonymous User",
contactInfo: rawUser.email
? `${rawUser.email}${rawUser.phone ? ` | ${formatPhone(rawUser.phone)}` : ''}`
: "No contact info provided",
status: rawUser.lastLogin
? (new Date() - new Date(rawUser.lastLogin)) / (1000 * 60 * 60 * 24) < 7
? "Active"
: "Inactive"
: "New User",
membership: (rawUser.memberSince && rawUser.subscriptionTier)
? `${rawUser.subscriptionTier} (Member for ${getMembershipDuration(rawUser.memberSince)})`
: "Non-member",
profileComplete: calculateProfileCompleteness(rawUser) + "%"
};
}
// Sample usage
const userData = {
username: "jsmith",
firstName: "John",
lastName: "Smith",
email: "john.smith@example.com",
phone: "5551234567",
lastLogin: "2025-04-20T14:30:00Z",
memberSince: "2024-01-15T09:15:00Z",
subscriptionTier: "Premium",
profileImg: "https://example.com/profiles/jsmith.jpg",
bio: "Software developer with a passion for JavaScript",
location: "San Francisco, CA"
};
const formattedUser = formatUserData(userData);
Dynamic Styling with Conditional Expressions
// Generate CSS classes based on data state
function getButtonClasses(button) {
return `btn
${button.size === 'large' ? 'btn-lg' :
button.size === 'small' ? 'btn-sm' : 'btn-md'}
${button.isPrimary ? 'btn-primary' : 'btn-secondary'}
${button.isOutlined ? 'btn-outlined' : ''}
${button.isDisabled ? 'disabled' : ''}
${button.hasIcon ? 'with-icon' : ''}
${button.isFullWidth ? 'btn-block' : ''}
${button.isLoading ? 'btn-loading' : ''}`.trim().replace(/\s+/g, ' ');
}
// Sample usage
const buttonState = {
size: 'large',
isPrimary: true,
isOutlined: false,
isDisabled: false,
hasIcon: true,
isFullWidth: true,
isLoading: false
};
const element = document.getElementById('submit-button');
element.className = getButtonClasses(buttonState); // "btn btn-lg btn-primary with-icon btn-block"
Complex Filtering and Sorting
// Filter and sort a product list with multiple criteria
function filterProducts(products, filters) {
return products
.filter(product => {
// Apply all active filters using logical operators
return (
// Category filter
(!filters.category || product.category === filters.category) &&
// Price range filter
(!filters.minPrice || product.price >= filters.minPrice) &&
(!filters.maxPrice || product.price <= filters.maxPrice) &&
// Rating filter
(!filters.minRating || product.rating >= filters.minRating) &&
// Availability filter
(!filters.inStockOnly || product.stock > 0) &&
// Search term filter (check in name and description)
(!filters.searchTerm ||
product.name.toLowerCase().includes(filters.searchTerm.toLowerCase()) ||
product.description.toLowerCase().includes(filters.searchTerm.toLowerCase()))
);
})
.sort((a, b) => {
// Apply sorting based on selected sort option
switch(filters.sortBy) {
case 'priceAsc':
return a.price - b.price;
case 'priceDesc':
return b.price - a.price;
case 'rating':
return b.rating - a.rating;
case 'newest':
return new Date(b.dateAdded) - new Date(a.dateAdded);
case 'popularity':
return b.salesCount - a.salesCount;
case 'name':
return a.name.localeCompare(b.name);
default:
return 0;
}
});
}
// Sample usage
const productFilters = {
category: "electronics",
minPrice: 100,
maxPrice: 500,
minRating: 4,
inStockOnly: true,
searchTerm: "bluetooth",
sortBy: "rating"
};
const filteredAndSortedProducts = filterProducts(allProducts, productFilters);
Debugging Common Operator and Expression Issues
Common Pitfalls and Solutions
Unexpected Type Conversion
// Problem: Unexpected string concatenation instead of addition
let score = "10";
let bonus = 5;
let total = score + bonus; // "105" (string concatenation) instead of 15
// Solution: Explicitly convert the string to a number
let total = Number(score) + bonus; // 15
// Problem: Comparing numbers with strings
let threshold = "100";
let value = 100;
if (value == threshold) { // true (loose equality)
console.log("Equal");
}
// Solution: Use strict equality
if (value === threshold) { // false (different types)
console.log("Equal types and values");
}
Incorrect Operator Precedence
// Problem: Unexpected order of operations
let result = 2 + 3 * 4; // 14, not 20
let condition = x > 5 && y > 5 || z > 5; // Might not behave as expected
// Solution: Use parentheses to clarify intent
let result = (2 + 3) * 4; // 20
let condition = (x > 5 && y > 5) || z > 5; // Clear grouping
Assignment vs. Equality Check
// Problem: Using assignment instead of comparison
let x = 10;
if (x = 5) { // Assigns 5 to x and then evaluates to 5 (truthy)
console.log("This always runs");
}
console.log(x); // 5
// Solution: Use equality operator
let x = 10;
if (x === 5) { // Compares x to 5
console.log("This only runs if x is 5");
}
Short-Circuit Evaluation Errors
// Problem: Relying on short-circuit with side effects
function processUser(user) {
// This may not execute the function if user is falsy
let isAdmin = user && user.checkAdminStatus();
// This might cause an error if user is null/undefined
let userName = user.name || "Guest";
}
// Solution: More explicit conditional checks
function processUser(user) {
// More explicit handling
let isAdmin = false;
if (user) {
isAdmin = user.checkAdminStatus();
}
// Safe property access
let userName = user ? user.name || "Guest" : "Guest";
}
Using Console to Debug Expressions
// Log intermediate values in complex expressions
let complex = a * (b + c) / d;
console.log("a:", a, "b:", b, "c:", c, "d:", d);
console.log("b + c:", b + c);
console.log("a * (b + c):", a * (b + c));
console.log("Final result:", complex);
// Debugging logical conditions
let condition = age >= 18 && (hasID || hasPassport) && !isRestricted;
console.log("Age check:", age >= 18);
console.log("ID check:", hasID || hasPassport);
console.log("Restriction check:", !isRestricted);
console.log("Full condition:", condition);
Using Browser Debugger
The browser's built-in debugger is a powerful tool for examining expressions step by step:
- Add the
debugger;statement in your code where you want to pause execution - Use the browser's developer tools to step through code and see each expression evaluation
- Add watch expressions to monitor specific values
- Examine the call stack and scope variables
function calculateTotal(items) {
let subtotal = 0;
debugger; // Execution will pause here when dev tools are open
for (let i = 0; i < items.length; i++) {
subtotal += items[i].price * items[i].quantity;
}
const tax = subtotal * 0.07;
const shipping = subtotal > 50 ? 0 : 9.99;
const total = subtotal + tax + shipping;
return {
subtotal,
tax,
shipping,
total
};
}
Best Practices for Working with Operators and Expressions
Clarity and Readability
- Use parentheses liberally to clarify the intended order of operations
- Break complex expressions into smaller, named variables
- Choose clarity over brevity when writing expressions
- Avoid deep nesting of conditional (ternary) operators
- Include whitespace around operators for better readability
Correctness and Safety
- Use strict equality operators (=== and !==) rather than loose equality (== and !=)
- Be careful with type conversions and make them explicit when needed
- Guard against potential nulls/undefined with optional chaining or checks
- Avoid assignments in conditional expressions to prevent accidental bugs
- Consider edge cases like division by zero or empty arrays
Maintenance and Future-Proofing
- Extract complex logic into well-named functions
- Comment on non-obvious expressions to explain intent
- Use constants for fixed values rather than magic numbers
- Test expressions with a variety of inputs to verify correctness
- Consider using modern JavaScript features like optional chaining and nullish coalescing
Before and After: Improving Expression Clarity
Before
// Hard to read and understand
function calculatePrice(product, quantity, coupon, user) {
return (product.price * (1 - (coupon && coupon.valid && !coupon.expired ?
coupon.percentOff / 100 : 0)) * quantity) *
(1 + (product.taxable ? 0.07 : 0)) +
(quantity * product.price < 50 && !user.premiumMember ? 4.99 : 0);
}
After
function calculatePrice(product, quantity, coupon, user) {
// Calculate base price
const basePrice = product.price * quantity;
// Apply discount if valid coupon
const hasCouponDiscount = coupon && coupon.valid && !coupon.expired;
const discountRate = hasCouponDiscount ? coupon.percentOff / 100 : 0;
const discountedPrice = basePrice * (1 - discountRate);
// Apply tax if product is taxable
const TAX_RATE = 0.07;
const taxAmount = product.taxable ? discountedPrice * TAX_RATE : 0;
// Apply shipping fee if order is small and user isn't premium
const SHIPPING_THRESHOLD = 50;
const SHIPPING_FEE = 4.99;
const qualifiesForFreeShipping = basePrice >= SHIPPING_THRESHOLD || user.premiumMember;
const shippingCost = qualifiesForFreeShipping ? 0 : SHIPPING_FEE;
// Return total price
return discountedPrice + taxAmount + shippingCost;
}
Additional Resources
Deepen your understanding of JavaScript operators and expressions with these valuable resources:
Documentation
- MDN: Expressions and Operators - Comprehensive reference and examples
- MDN: Operator Precedence - Detailed precedence table
Interactive Learning
- JavaScript.info: Operators - Clear explanations with interactive examples
- Codecademy: JavaScript Course - Step-by-step interactive learning
Tools for Practice
- JS Console - Simple JavaScript console for testing expressions
- JavaScript Operator Tester - Tool to experiment with operators
Practice Exercises
Reinforce your understanding with these hands-on exercises:
Basic Exercises
- Calculator: Create a function that takes two numbers and an operator (+, -, *, /, %) as arguments and returns the result
- Temperature Converter: Write functions to convert between Celsius and Fahrenheit using arithmetic operators
- User Validation: Create expressions to check if a username meets requirements (minimum length, no spaces, etc.)
Intermediate Exercises
- Shopping Cart Total: Calculate a cart total with tax, discounts, and shipping based on various conditions
- Password Strength Checker: Use logical operators to verify password criteria (length, uppercase, numbers, special chars)
- Dynamic Greeting: Create a function that returns different greetings based on time of day, user status, and language preference
Advanced Exercises
- Permission System: Implement a bitwise operator-based permission system with functions to check, grant, and revoke permissions
- Form Field Validator: Build a robust validation system for form fields with detailed error messages
- Expression Evaluator: Create a function that can evaluate mathematical expressions given as strings, respecting operator precedence
Challenge: Mini Expression Parser
Create a simple expression parser that can evaluate basic arithmetic expressions with proper operator precedence.
// Example challenge starter code
function evaluateExpression(expr) {
// Remove all whitespace
expr = expr.replace(/\s+/g, '');
// TODO: Implement a parser that respects operator precedence
// Hint: Try using a recursive approach to handle parentheses first,
// then multiplication/division, then addition/subtraction
return result;
}
// Test with expressions like:
// evaluateExpression("3 + 4 * 2"); // Should return 11
// evaluateExpression("(3 + 4) * 2"); // Should return 14
// evaluateExpression("10 / 2 + 3"); // Should return 8
Key Takeaways
- JavaScript operators allow you to perform operations on values like arithmetic, assignment, comparison, and logical operations
- Expressions are units of code that produce values, from simple variables to complex calculations
- Understanding operator precedence is crucial for writing correct expressions
- Type conversion can cause unexpected results; use strict equality (===) for safer comparisons
- Use parentheses and intermediate variables to make complex expressions more readable
- Logical operators can short-circuit, which is both a powerful tool and a potential source of bugs
- Modern JavaScript provides helpful operators like nullish coalescing (??) and optional chaining (?.) for safer expressions