Advanced JavaScript
Modern Syntax Foundations
A Practical Guide
2026
Introduction to This Book
Chapter 1: let and const
Why they exist
let
const
The Temporal Dead Zone (TDZ)
Best practices
Chapter 2: Template Strings (Template Literals)
Basic interpolation
Multi-line strings
Nesting and tagged templates
Best practices
Chapter 3: Arrow Functions
Syntax variations
Lexical this
What arrow functions cannot do
Best practices
Chapter 4: The Rest Operator
Rest parameters in functions
Rest in destructuring
Rest vs. arguments
Best practices
Chapter 5: The Spread Operator
Spreading arrays
Spreading objects
Spreading strings
Spread vs. [Link] / concat
Best practices
Chapter 6: Object Literals (Enhancements)
Property shorthand
Method shorthand
Computed property names
Combining features
Best practices
Chapter 7: Destructuring Arrays
Basics
Skipping items
Default values
Swapping variables
Destructuring function returns
Nested array destructuring
Best practices
Chapter 8: Destructuring Objects
Basics
Renaming while destructuring
Default values
Nested object destructuring
Destructuring function parameters
Combining rest with object destructuring
Best practices
Part 1 Summary
Introduction to This Book
Modern JavaScript (ES6 and beyond) changed how the language is written day to day. This book is a practical, example-driven
tour of the features and APIs that separate “I know JavaScript” from “I write modern, professional JavaScript.” Each chapter
explains a concept clearly, shows working code, and calls out common mistakes.
This is Part 1 of 8, covering the syntax foundations every advanced JavaScript developer relies on constantly: let / const ,
template strings, arrow functions, rest/spread, object literals, and destructuring.
Chapter 1: let and const
Why they exist
Before ES6, var was the only way to declare a variable. var is function-scoped, gets hoisted with a confusing “declared but
undefined” state, and can be redeclared silently — all common sources of bugs. let and const fix this by being block-scoped.
let
let declares a variable that can be reassigned, but only exists within the nearest enclosing block ( { ... } ).
if (true) {
let x = 10;
[Link](x); // 10
}
[Link](typeof x); // "undefined" - x doesn't exist here
for (let i = 0; i < 3; i++) {
setTimeout(() => [Link](i), 100); // 0, 1, 2 (each loop gets its own i)
}
With var , the loop example above would print 3, 3, 3 , because all callbacks would share a single function-scoped i . let
creates a fresh binding per iteration, which is why it behaves correctly in closures.
const
const also is block-scoped, but it cannot be reassigned after initialization. It must be initialized at declaration time.
const PI = 3.14159;
PI = 3; // TypeError: Assignment to constant variable.
const user = { name: "Alice" };
[Link] = "Bob"; // OK! const only prevents reassigning the binding,
// not mutating the object it points to.
user = {}; // TypeError
The Temporal Dead Zone (TDZ)
let and const are hoisted to the top of their block, but unlike var , they are not initialized until the declaration line executes.
Accessing them before that point throws an error rather than returning undefined .
[Link](a); // ReferenceError: Cannot access 'a' before initialization
let a = 5;
Best practices
Default to const . Only use let when you know the variable must be reassigned.
Never use var in new code.
Treat the TDZ as a feature: it catches bugs where you’d otherwise silently use undefined .
Chapter 2: Template Strings (Template Literals)
Template literals use backticks ( ` ) instead of quotes and support embedded expressions and multi-line text.
Basic interpolation
const name = "Alice";
const age = 30;
const greeting = `Hello, ${name}! You are ${age} years old.`;
[Link](greeting); // Hello, Alice! You are 30 years old.
// Expressions, not just variables:
const price = 19.99;
[Link](`Total: $${(price * 1.08).toFixed(2)}`);
Multi-line strings
const message = `Dear customer,
Thank you for your order.
It will ship within 2 business days.`;
No more "line one\n" + "line two" concatenation.
Nesting and tagged templates
Template literals can nest, and can be processed by a tag function — a function that receives the literal pieces and the
interpolated values separately, useful for sanitizing input or building DSLs (like styled-components).
function highlight(strings, ...values) {
return [Link]((result, str, i) => {
const value = values[i] ? `**${values[i]}**` : "";
return result + str + value;
}, "");
}
const item = "Laptop";
const stock = 3;
[Link](highlight`Item: ${item}, Stock left: ${stock}`);
// Item: **Laptop**, Stock left: **3**
Best practices
Prefer template literals over string concatenation everywhere.
Use tagged templates for escaping (e.g., SQL, HTML) rather than manual string building.
Chapter 3: Arrow Functions
Arrow functions offer shorter syntax and, more importantly, lexical this — they don’t create their own this binding, and
instead inherit it from the enclosing scope.
Syntax variations
// Traditional function
function add(a, b) { return a + b; }
// Arrow function, full form
const add2 = (a, b) => { return a + b; };
// Implicit return (no braces = return the expression)
const add3 = (a, b) => a + b;
// Single parameter: parens optional
const square = x => x * x;
// No parameters
const greet = () => "Hello!";
// Returning an object literal needs parentheses
const makeUser = (name) => ({ name, active: true });
Lexical this
class Timer {
constructor() {
[Link] = 0;
}
start() {
// Arrow function inherits `this` from start() -> the Timer instance
setInterval(() => {
[Link]++;
[Link]([Link]);
}, 1000);
}
}
new Timer().start(); // 1, 2, 3, ... correctly refers to the Timer instance
With a regular function instead, this inside setInterval ’s callback would be undefined (in strict mode) or the global object —
not the Timer.
What arrow functions cannot do
No own this , arguments , super , or [Link] — they’re unsuitable as object methods that rely on this , and they can
never be used as constructors ( new arrowFn() throws).
No arguments object — use rest parameters instead (Chapter 4).
const obj = {
value: 42,
getValue: () => [Link] // BUG: `this` is NOT obj here
};
[Link]([Link]()); // undefined
Best practices
Use arrow functions for callbacks, array methods, and anything that should inherit the surrounding this .
Use regular function (or method shorthand) for object methods and anything used with new .
Chapter 4: The Rest Operator
The rest operator ( ... ) collects multiple elements into a single array or object. It always appears on the receiving side
(function parameters, destructuring targets).
Rest parameters in functions
function sum(...numbers) {
return [Link]((total, n) => total + n, 0);
}
sum(1, 2, 3); // 6
sum(1, 2, 3, 4, 5); // 15
Rest parameters must be the last parameter, and there can only be one.
function logOrder(orderId, ...items) {
[Link](`Order ${orderId}:`, items);
}
logOrder(101, "Book", "Pen", "Notebook");
// Order 101: [ 'Book', 'Pen', 'Notebook' ]
Rest in destructuring
const [first, second, ...rest] = [10, 20, 30, 40, 50];
[Link](first, second, rest); // 10 20 [30, 40, 50]
const { id, ...otherFields } = { id: 1, name: "Alice", age: 30 };
[Link](id, otherFields); // 1 { name: 'Alice', age: 30 }
Rest vs. arguments
Unlike the old arguments object, rest parameters are real arrays (so .map , .filter , .reduce work directly) and they only
capture parameters that weren’t explicitly named.
function old() {
[Link](arguments); // Arguments(3) [1, 2, 3] - array-like, not a real array
}
old(1, 2, 3);
Best practices
Prefer rest parameters over arguments in all new code.
Use rest in destructuring to peel off “everything else” cleanly.
Chapter 5: The Spread Operator
Spread looks identical to rest ( ... ) but does the opposite: it expands an iterable or object into individual elements. It’s used on
the source side (function calls, array/object literals).
Spreading arrays
const nums = [1, 2, 3];
[Link]([Link](...nums)); // 3, equivalent to [Link](1, 2, 3)
const a = [1, 2];
const b = [3, 4];
const combined = [...a, ...b]; // [1, 2, 3, 4]
const copy = [...nums]; // shallow copy, not a reference
[Link](4);
[Link](nums); // [1, 2, 3] - original untouched
Spreading objects
const defaults = { theme: "light", fontSize: 14 };
const userPrefs = { fontSize: 18 };
const settings = { ...defaults, ...userPrefs };
[Link](settings); // { theme: 'light', fontSize: 18 }
// Later spreads override earlier keys - order matters.
Spreading strings
const letters = [..."hello"];
[Link](letters); // ['h', 'e', 'l', 'l', 'o']
Spread vs. [Link] / concat
Spread is largely a cleaner, more readable replacement for [Link]({}, a, b) and [Link](arr2) , though it’s
important to remember spread creates a shallow copy — nested objects/arrays are still shared by reference.
const original = { user: { name: "Alice" } };
const clone = { ...original };
[Link] = "Bob";
[Link]([Link]); // "Bob" - nested object was shared!
Best practices
Use spread for immutable updates in state management (React, Redux) — it avoids mutating the original.
Remember it’s shallow; for deep copies use structuredClone() or a dedicated utility.
Chapter 6: Object Literals (Enhancements)
ES6 added several shorthand features that make object literals faster to write and easier to read.
Property shorthand
const name = "Alice";
const age = 30;
// Old way
const user1 = { name: name, age: age };
// Shorthand
const user2 = { name, age };
[Link](user2); // { name: 'Alice', age: 30 }
Method shorthand
const calculator = {
// Old way: total: function(a, b) { return a + b; }
total(a, b) {
return a + b;
}
};
[Link]([Link](2, 3)); // 5
Computed property names
const key = "score";
const dynamicKey = "level_1";
const player = {
[key]: 100,
[`bonus_${dynamicKey}`]: 50
};
[Link](player); // { score: 100, bonus_level_1: 50 }
Combining features
function createUser(name, role, extraFields = {}) {
return {
name,
role,
createdAt: new Date().toISOString(),
...extraFields,
[`is${role}`]: true
};
}
[Link](createUser("Alice", "Admin", { active: true }));
Best practices
Use shorthand syntax by default; it reduces noise and repetition.
Use computed keys instead of building objects with bracket assignment after creation.
Chapter 7: Destructuring Arrays
Array destructuring unpacks values by position into distinct variables.
Basics
const coords = [10, 20, 30];
const [x, y, z] = coords;
[Link](x, y, z); // 10 20 30
Skipping items
const [first, , third] = [1, 2, 3];
[Link](first, third); // 1 3
Default values
const [a = 1, b = 2] = [10];
[Link](a, b); // 10 2 (b falls back to default since it's missing)
Swapping variables
let p = 1, q = 2;
[p, q] = [q, p];
[Link](p, q); // 2 1
Destructuring function returns
function getCoordinates() {
return [51.5074, -0.1278];
}
const [lat, lng] = getCoordinates();
[Link](lat, lng); // 51.5074 -0.1278
Nested array destructuring
const matrix = [[1, 2], [3, 4]];
const [[a1, a2], [b1, b2]] = matrix;
[Link](a1, a2, b1, b2); // 1 2 3 4
Best practices
Use destructuring to give meaningful names to values coming from arrays (e.g., useState() in React returns [value,
setValue] ).
Combine with rest (Chapter 4) to grab “the first N and everything else.”
Chapter 8: Destructuring Objects
Object destructuring unpacks values by property name rather than position.
Basics
const user = { name: "Alice", age: 30, city: "London" };
const { name, age } = user;
[Link](name, age); // Alice 30
Renaming while destructuring
const { name: userName, age: userAge } = user;
[Link](userName, userAge); // Alice 30
Default values
const { name, country = "Unknown" } = user;
[Link](country); // "Unknown" - not present on user, so default is used
Nested object destructuring
const response = {
status: 200,
data: { id: 1, profile: { email: "alice@[Link]" } }
};
const { data: { profile: { email } } } = response;
[Link](email); // alice@[Link]
Destructuring function parameters
This is one of the most common uses in real code — especially for options objects.
function createButton({ label, color = "blue", onClick }) {
[Link](`Rendering "${label}" button in ${color}`);
onClick();
}
createButton({ label: "Submit", onClick: () => [Link]("Clicked!") });
Combining rest with object destructuring
const { name, ...rest } = { name: "Alice", age: 30, city: "London" };
[Link](name, rest); // Alice { age: 30, city: 'London' }
Best practices
Destructure function parameters for options objects instead of long positional parameter lists.
Use renaming to avoid variable name collisions when pulling from multiple sources.
Part 1 Summary
Feature Purpose
let / const Block-scoped variable declarations, safer than var
Template literals String interpolation and multi-line strings
Arrow functions Concise functions with lexical this
Rest operator Collect multiple values into an array/object
Spread operator Expand an array/object into individual values
Object literal shorthand Shorter, cleaner object creation
Array destructuring Unpack values by position
Object destructuring Unpack values by property name
Next: Part 2 — Object-Oriented JavaScript, covering OOP principles, class inheritance, and the modulus operator.