2.4 Advanced Operators
Logical Operators and Short-Circuit Evaluation
JavaScript's logical operators don't simply return true/false — they return the actual operand value. This is called Short-Circuit Evaluation.
&& (AND) Operator
Returns the left value if falsy, otherwise returns the right value.
console.log(1 && 2); // 2 (left is truthy → returns right)
console.log(0 && 2); // 0 (left is falsy → returns left, right not evaluated)
console.log(null && "value"); // null
// Practical pattern: conditional execution
const user = { name: "Alice", isAdmin: true };
user.isAdmin && console.log("Admin user"); // Executes
// Conditional rendering (common in React)
const isLoggedIn = true;
const element = isLoggedIn && "<div>Welcome!</div>";
|| (OR) Operator
Returns the left value if truthy, otherwise returns the right value.
console.log(1 || 2); // 1 (left is truthy → returns left)
console.log(0 || 2); // 2 (left is falsy → returns right)
console.log("" || "default"); // "default"
console.log(null || "alt"); // "alt"
// Default value pattern (traditional)
function greet(name) {
const displayName = name || "Anonymous";
return `Hello, ${displayName}!`;
}
greet(""); // "Hello, Anonymous!" (empty string also gets default!)
greet(0); // "Hello, Anonymous!" (0 also gets default!)
?? (Nullish Coalescing Operator)
Problem with ||: valid falsy values like 0, "", false also get replaced with defaults.
?? returns the right side only when null or undefined.
const count = 0;
console.log(count || 10); // 10 (0 is falsy → uses default)
console.log(count ?? 10); // 0 (0 is not null/undefined → keeps 0)
const name = "";
console.log(name || "anonymous"); // "anonymous" (empty string is falsy)
console.log(name ?? "anonymous"); // "" (empty string is not null/undefined)
// Practical use
function getConfig(userConfig) {
return {
timeout: userConfig.timeout ?? 3000, // 0 is valid
retries: userConfig.retries ?? 3,
debug: userConfig.debug ?? false, // false is valid
};
}
const config = getConfig({ timeout: 0, retries: 0, debug: false });
console.log(config); // { timeout: 0, retries: 0, debug: false }
// With ||, all would have been replaced with defaults!
?. (Optional Chaining)
Returns undefined instead of throwing an error when accessing properties of null or undefined.
const user = {
name: "Alice",
address: { city: "New York" },
getGreeting() { return `Hello, ${this.name}!`; },
};
// Optional chaining
const city = user?.address?.city; // "New York"
const country = user?.address?.country; // undefined (no error)
const guest = null;
console.log(guest?.name); // undefined (no error)
console.log(guest?.getGreeting()); // undefined (no error)
// Array access
const arr = [1, 2, 3];
const empty = null;
console.log(empty?.[0]); // undefined
// Combining with ?? (very useful!)
const userName = user?.profile?.displayName ?? user?.name ?? "Anonymous";
console.log(userName); // "Alice"
Logical Assignment Operators (ES2021)
// ||= (OR assign): assigns right side only when left is falsy
let a = null;
a ||= "default";
console.log(a); // "default"
// &&= (AND assign): assigns right side only when left is truthy
let c = "original";
c &&= "updated";
console.log(c); // "updated"
// ??= (Nullish assign): assigns only when null/undefined
let e = null;
e ??= "default";
console.log(e); // "default"
let f = 0;
f ??= 100;
console.log(f); // 0 (0 is not null/undefined)
// Cache pattern
const cache = {};
function getUser(id) {
cache[id] ??= fetchUserFromDB(id); // Only query DB if not cached
return cache[id];
}
Spread Operator
... spreads iterables into individual elements.
// Array spread
const arr1 = [1, 2, 3];
const arr2 = [4, 5, 6];
const combined = [...arr1, ...arr2];
console.log(combined); // [1, 2, 3, 4, 5, 6]
// Object spread (ES2018)
const defaults = { theme: "light", lang: "en", fontSize: 14 };
const userPrefs = { theme: "dark", fontSize: 16 };
const settings = { ...defaults, ...userPrefs }; // Later values override earlier
console.log(settings);
// { theme: "dark", lang: "en", fontSize: 16 }
// Deduplication
const unique = [...new Set([1, 2, 2, 3, 3, 3])];
console.log(unique); // [1, 2, 3]
Pro Tips
Combining Optional Chaining and Nullish Coalescing
// Very useful for API response handling
async function fetchUserProfile(userId) {
const response = await fetch(`/api/users/${userId}`);
const data = await response.json();
return {
name: data?.user?.profile?.displayName ?? data?.user?.name ?? "Unknown",
avatar: data?.user?.avatar?.url ?? "/default-avatar.png",
bio: data?.user?.bio ?? "",
followerCount: data?.user?.stats?.followers ?? 0,
};
}