Lesson 25 of 33
Modern JavaScript Features
The modern ES2020+ features you will reach for most: optional chaining, nullish coalescing, logical assignment operators, BigInt and structuredClone.
JavaScript gains new features every year with the release of a new ECMAScript standard (ES2020, ES2021, ES2022, ES2023, and ES2024).
These are not only shorter to type. They remove whole patterns of defensive boilerplate, prevent some common runtime errors, and make code easier to read when you come back to it months later.
Optional Chaining (?.)
Safely reads nested properties. Returns undefined immediately if any step is null or undefined, preventing runtime crashes.
Nullish Coalescing (??)
Provides default fallback values only when the value is null or undefined (preserves valid values like 0, false, and '').
structuredClone()
Built-in browser method for true deep copying of nested objects, arrays, and dates.
Here are the ones you will reach for most often.
Optional chaining (?.)
Before optional chaining, accessing deeply nested properties on objects was risky. If any property in the chain was null or undefined, your program would stop with the familiar TypeError: Cannot read properties of undefined.
Take this user, who has no address recorded:
const user = {
name: "Alex",
tags: ["admin", "beta"],
};Guarding every step by hand is tedious:
// The old way:
let streetName;
if (user && user.address && user.address.street) {
streetName = user.address.street.name;
}With optional chaining (?.), JavaScript checks if the value before ?. is null or undefined. If it is, it stops evaluating immediately and returns undefined:
// The modern way:
const streetName = user?.address?.street?.name;
console.log(streetName); // undefined, because address is missingOne distinction that trips people up: ?. protects you from null and undefined values, not from names that were never declared. If user itself does not exist as a variable, user?.address still throws a ReferenceError.
You can also use optional chaining for method calls and array indexing:
// Call a method only if it exists:
user.onSave?.();
// Index an array only if the array exists:
const firstTag = user.tags?.[0];Nullish coalescing (??)
In JavaScript, developers often provided default values using the logical OR operator (||):
const userCount = response.count || 10;The bug? || checks for falsy values (false, 0, "", NaN, null, undefined).
If response.count is 0, which is a perfectly valid count, 0 is falsy, so userCount is incorrectly set to 10.
The nullish coalescing operator (??) fixes this by falling back only if the value is null or undefined:
const score = 0;
console.log(score || 100); // 100 -- the bug: 0 was treated as missing
console.log(score ?? 100); // 0 -- correct, 0 is kept
const nickname = null;
console.log(nickname ?? "Guest"); // "Guest"Logical assignment operators (||=, &&=, ??=)
Just like += and -=, JavaScript provides concise logical assignment operators:
const config = {};
// Default config.theme to "dark" only if config.theme is null or undefined
config.theme ??= "dark";
console.log(config.theme); // "dark"
// A stand-in for a real session check, which would ask the server:
function checkSession() {
return false;
}
// Re-check the session only if the user was logged in to begin with
const session = { loggedIn: true, lastSeen: null };
session.loggedIn &&= checkSession();
console.log(session.loggedIn); // falseDeep copies with structuredClone()
Spread syntax ({ ...obj }) and Object.assign() only perform a shallow copy, so nested objects and arrays inside are still shared by reference.
Before this existed, developers reached for tricks like JSON.parse(JSON.stringify(obj)), which quietly breaks Date objects and functions, or pulled in an external library such as Lodash.
Modern JavaScript gives us structuredClone():
const original = {
name: "Game Session",
settings: { volume: 80, difficulty: "hard" },
items: ["sword", "shield"]
};
// A native deep clone:
const deepCopy = structuredClone(original);
// Modifying the nested copy does not affect the original
deepCopy.settings.volume = 20;
console.log(original.settings.volume); // still 80Very large integers with BigInt
Standard numbers in JavaScript lose precision past 9,007,199,254,740,991 (Number.MAX_SAFE_INTEGER).
For database IDs, cryptographic computations, or very large counts, append an n to a number to create a BigInt:
const hugeNumber = 9007199254740991n + 5n;
console.log(hugeNumber); // 9007199254740996nTry it yourself
Run this, then swap the ?? for || and watch fontSize change from 0 to 14 — the exact bug the operator exists to prevent.
What to remember
These features make code shorter and safer at the same time. Use ?. to read nested properties that might not be there, ?? for defaults that respect 0 and false, structuredClone() when a shallow copy is not enough, and the logical assignment operators when you are updating a value based on what it already holds.
Check yourself
4 questions · pass 3/4 to unlock Modules
1.What does the optional chaining operator ?. do when evaluating user?.address?.street?
2.How does the nullish coalescing operator ?? differ from the logical OR operator ||?
3.What is the cleanest modern built-in function to create a deep clone of an object in JavaScript?
4.Why was BigInt added to JavaScript?
4 left to answer