ES6+ Modern Syntax
ES6+ Modern Syntax
ES6 (2015) and the yearly releases since transformed how JavaScript is written. The features below are not optional extras — they are the default dialect of every modern codebase, framework tutorial, and code review. Learn what each one does and the specific edge cases where beginners get burned.
Step 1: Template Literals
const name = "World";
const greeting = Hello, ${name}!;
// Multi-line strings — no more "\n" and + concatenation
const html =
${title}
${content}
;
Why it works: backtick strings evaluate any JavaScript expression inside ${...} — not just variables but calls and arithmetic, like ${price * qty} — and preserve line breaks literally. Compared to "Hello, " + name + "!", there are no missing-space bugs and no operator precedence surprises.
Step 2: Destructuring
// Arrays — position based
const [first, second, ...rest] = [1, 2, 3, 4, 5];
// Objects — name based, with rename and defaults
const user = { name: "Ada", age: 36 };
const { name: userName, age, city = "Unknown" } = user;
// Straight into function parameters
function greet({ name, age }) {
console.log(${name} is ${age} years old);
}
greet(user);
Why it works: destructuring is pattern matching against a value's shape. Object patterns look properties up by name (order is irrelevant), array patterns by position. Defaults apply only when the value is undefined. One trap: destructuring null or undefined itself throws — const { id } = maybeUser crashes if maybeUser is null. Guard with a default: const { id } = maybeUser ?? {}.
Step 3: Spread and Rest
// Spread expands; rest collects
const arr1 = [1, 2, 3];
const arr2 = [...arr1, 4, 5]; // [1, 2, 3, 4, 5]
const obj1 = { a: 1, b: 2 };
const obj2 = { ...obj1, c: 3 }; // { a: 1, b: 2, c: 3 }
Math.max(...arr1); // 3 — spread as arguments
function tally(...scores) { // rest in parameters
return scores.length;
}
Why copies matter — and their limit: { ...obj1 } and [...arr1] create new top-level containers, which is exactly what React-style immutable updates require. But the copy is shallow: nested objects are still shared references, so copy.address.city = "X" also changes the original. Deep structures need structuredClone().
Later keys win in object spread, which makes clean "update" expressions: const updated = { ...settings, theme: "dark" }.
Step 4: Optional Chaining (?.)
const user = { profile: { name: "John" } };
const name = user?.profile?.name; // "John"
const zip = user?.address?.zip; // undefined — no crash
user?.getName?.(); // call only if it exists
items?.[0]; // safe index access
Why it works: ?. checks whether the left side is null or undefined; if so, the whole expression short-circuits to undefined instead of throwing Cannot read properties of undefined. Use it at genuinely uncertain boundaries (API responses, config), not on every dot — over-chaining hides real bugs where data should always exist.
Step 5: Nullish Coalescing (??)
const a = null ?? "default"; // "default"
const b = 0 ?? "default"; // 0 — || would wrongly give "default"
const c = "" ?? "default"; // "" — || would wrongly give "default"
const port = config.port ?? 3000;
Why ?? exists: the older || fallback treats every falsy value — 0, "", false — as missing. ?? only falls back on null/undefined, so legitimate zeros and empty strings survive. Any time a valid value could be falsy, || is a bug and ?? is the fix.
Step 6: Object Shorthand
const name = "John";
const age = 30;
const user = { name, age }; // { name: "John", age: 30 }
const api = {
greet() { // method shorthand
return "Hello!";
},
["key_" + age]: true // computed property names
};
When the variable name matches the property name, write it once. This pairs beautifully with destructuring on the way in and shorthand on the way out.
Common Beginner Errors and Fixes
- Destructuring null:
const { id } = apiResultthrows when the result is null. Fix:apiResult ?? {}. - Shallow-copy surprise: editing a nested object after spreading changes the original too. Fix: spread each level you modify, or
structuredClone. - || eating valid zeros:
count || 10returns 10 when count is legitimately 0. Fix:count ?? 10. - Quotes instead of backticks:
"Hello, ${name}"prints the dollar-brace literally. Fix: template substitution only works inside backticks. - Overusing ?. as error handling: chains of
a?.b?.c?.dsilently produce undefined where a thrown error would have shown you the broken assumption. Fix: reserve?.for data that is expected to sometimes be absent.
Practice Exercise
You receive this API response:
const response = {
user: { name: "Rin", stats: { posts: 0 } },
settings: null
};
name and posts in a single statement (hint: nested pattern).response.settings.theme safely with ?. and default it to "light" with ??.const summary = { name, posts, theme } using shorthand.response."Rin: 1 posts, light theme".Check yourself: if your step 2 used ||, would a saved theme of "" behave correctly? That question is exactly why ?? was added to the language.