1. Destructuring
Destructuring unpacks values out of an array or object into their own variables, in a single line, instead of accessing each one individually:
const coordinates = [10, 20, 30];
// Without destructuring
const x = coordinates[0];
const y = coordinates[1];
// With destructuring -- same result, one line
const [a, b, c] = coordinates;
console.log(a, b, c); // 10 20 30
// Skip elements you don't need with an empty slot
const [first, , third] = coordinates;
console.log(first, third); // 10 30
const user = { name: "Priya", age: 29, city: "Kolkata" };
// Pulls out properties BY NAME, in any order
const { age, name } = user;
console.log(name, age); // "Priya" 29
// Rename while destructuring, and provide a default for a missing property
const { name: userName, country = "India" } = user;
console.log(userName, country); // "Priya" "India"
Array destructuring matches by position — the first variable gets the first element, and so on. Object destructuring matches by property name instead, so the order the variables are listed in doesn't matter at all. Function parameters can be destructured directly too, which is extremely common for a function that takes one options object:
function greet({ name, greeting = "Hello" }) {
return `${greeting}, ${name}!`;
}
greet({ name: "Ada" }); // "Hello, Ada!"
greet({ name: "Ada", greeting: "Hi" }); // "Hi, Ada!"
2. Spread & Rest, Revisited
Week 2 covered rest parameters, and Week 6 covered spread for copying arrays and
objects — the same ... syntax also combines directly with
destructuring, which is worth seeing explicitly:
const [first, ...rest] = [1, 2, 3, 4];
console.log(first, rest); // 1 [2, 3, 4]
const { id, ...otherFields } = { id: 1, name: "Ada", age: 30 };
console.log(id, otherFields); // 1 { name: "Ada", age: 30 }
const numbers = [4, 2, 9, 1];
// Math.max normally takes individual arguments, not an array --
// spread expands the array into separate arguments
console.log(Math.max(...numbers)); // 9
console.log(Math.max(4, 2, 9, 1)); // 9 -- exactly equivalent
The direction is the useful thing to keep straight: rest gathers
multiple individual values into one array/object (used when
declaring — a parameter list or a destructuring pattern).
Spread expands one array/object out into individual values
(used when calling or building — a function call or a new array/object
literal). Same three dots, opposite direction, and which one you're looking at is
always determined by where the ... appears.
3. Optional Chaining & Nullish Coalescing
Two operators purpose-built for the single most common defensive-coding annoyance: safely reading a property that might not exist.
const user = { profile: { city: "Kolkata" } };
const guest = {};
console.log(user.profile?.city); // "Kolkata"
console.log(guest.profile?.city); // undefined -- NOT a crash
// Without ?., this would throw: "Cannot read properties of undefined"
// console.log(guest.profile.city);
const settings = { volume: 0, brightness: null };
console.log(settings.volume ?? 50); // 0 -- 0 is a real, intentional value
console.log(settings.volume || 50); // 50 -- WRONG here, || treats 0 as falsy too
console.log(settings.brightness ?? 50); // 50 -- brightness is genuinely missing
?. stops evaluating and returns undefined immediately the
moment it hits null or undefined, instead of throwing —
chain several together (a?.b?.c) to safely walk arbitrarily deep into
possibly-missing data. ?? provides a fallback only for
null/undefined specifically — unlike ||,
which also falls back on any falsy value (0, "",
false), which is very rarely what's actually intended.
4. ES Modules
Everything so far in this course has lived in one file. A real project splits code
across many files — ES modules are the standard, built-in way to
share code between them with export and import:
// Named exports: export as many of these as needed from one file
export function add(a, b) {
return a + b;
}
export const PI = 3.14159;
// Default export: at most ONE per file, for the file's "main" thing
export default function multiply(a, b) {
return a * b;
}
import multiply, { add, PI } from "./math.js";
// default export ^ ^ named exports, in curly braces
console.log(add(2, 3)); // 5
console.log(multiply(2, 3)); // 6
console.log(PI); // 3.14159
A file can have any number of named exports (imported with matching
names inside {'{'} {'}'}) but at most one default export
(imported under whatever name you choose, no braces). Each module also has its own
private scope by default — a variable declared in math.js that isn't
exported simply doesn't exist from main.js's point of view, which is
exactly the "avoid leaking into a shared scope" problem Week 3's IIFEs used to solve
by hand.
In the browser, a <script type="module"> tag enables import/export in that script. In Node, either name files .mjs, or set "type": "module" in package.json — Week 14's tooling section covers this setup directly.
5. Hands-on Exercise
Split a user-formatter into modules
Practice named/default exports, destructuring, and safe property access together.
Requirements:
- Create
users.jsthat exports (as named exports) an array of at least 4 user objects, each withname,age, and an optional nestedaddress: {'{'} city {'}'}(omit it on at least one user). - Create
format.jswith a default-exported functionformatUser(user)that destructuresnameandagefrom its parameter, uses?.to safely readaddress?.city, and??to fall back to"Unknown"if the city is missing. - In a
main.js, import both modules and logformatUser(user)for every user in the array. - Use array destructuring with rest (
const [firstUser, ...otherUsers] = users) to separately log the first user and a count of the rest. - Add one comment explaining why the user missing an
addressdoesn't crashformatUser.
If running this directly with Node, remember either .mjs file extensions or "type": "module" in a package.json next to the files — plain .js files run as CommonJS by default and don't understand import/export.
6. Knowledge Check
Four quick questions. Expand each to check your answer.
Q1
Given const { age, name } = user;, does the order of age and name in the braces need to match the order of properties on user?
Given const { age, name } = user;, does the order of age and name in the braces need to match the order of properties on user?
No. Object destructuring matches by property name, not position — {'{'} age, name {'}'} and {'{'} name, age {'}'} both pull the exact same values regardless of what order user's own properties happen to be in. (Array destructuring is the one that's positional — const [a, b] = arr genuinely does depend on order.)
Q2
What's the difference between rest and spread — they both use ...?
What's the difference between rest and spread — they both use ...?
Rest gathers multiple values into one array/object, used when declaring something — a function parameter list or a destructuring pattern (const [first, ...rest] = arr). Spread expands one array/object out into individual values, used when calling or building something — a function call or a new array/object literal (Math.max(...numbers)). Which one applies is always determined by where the ... appears.
Q3
Why is settings.volume ?? 50 safer than settings.volume || 50 when volume could legitimately be 0?
Why is settings.volume ?? 50 safer than settings.volume || 50 when volume could legitimately be 0?
|| falls back to its right-hand side for any falsy value, including 0, "", and false — so a genuinely intentional volume: 0 would incorrectly become 50. ?? only falls back when the left side is specifically null or undefined, leaving a real value like 0 untouched.
Q4
Can a single file have more than one default export?
Can a single file have more than one default export?
No — at most one export default per file. A file can have any number of named exports alongside it, but the default export is meant to represent that module's single "main" thing, and JavaScript enforces that limit directly (a second export default in the same file is a syntax error).