8.4 — ES6+ grab bag: ?. and ??
Back in 2.4 you learned || defaults and && guards — and a note said modern code upgrades them to ?? and ?., “Phase 8.” Welcome to the payoff. These two tiny operators kill the single most common error in JavaScript: reading a property off undefined — and fix a genuine bug hiding in ||.
Every API response, every optional field, every “this user has no pet” — this is the grammar real data is read with.
const user = { name: "Ada", pet: { name: "Rex" } };
// user.cat.name ← TypeError! program dead
console.log(user.pet?.name);
console.log(user.cat?.name);
const volume = 0;
console.log(volume || 50);
console.log(volume ?? 50);
const label = user.cat?.name ?? "no pet";
console.log(label);Meet the most common error in the language. user has no cat property, so user.cat evaluates to undefined (1.7) — and asking undefined for .name doesn’t answer undefined. It CRASHES: TypeError, program dead, nothing below ever runs.
The deeper story, with the real names for things — this part is what turns “I saw it” into “I can explain it.”
The family has two more members: user.greet?.() calls a method only if it exists, and arr?.[0] guards bracket lookups. Same rule everywhere: null/undefined on the left → stop, answer undefined.
The stop is total: in a?.b.c.d(), if a is missing, then .b, .c and the call all never happen — including any side effects they would have had. That’s why it’s called short-circuiting, the same word from 2.4.
A designed-in speed bump: you may not mix || and ?? bare — a || b ?? c is a SyntaxError. The language forces parentheses so nobody has to memorize which binds tighter. When in doubt, parenthesize — 1.10’s advice, now enforced by the grammar.
Job note: document.querySelector answers null when nothing matches (7.2), so el?.textContent is daily DOM grammar. And in API testing, response fields are optional by nature — body.user?.address?.city ?? "unknown" is the shape of half the assertions you’ll write on the job.
⌨️ the crash-proof pet reader
A user list where some users have a pet and some don’t — exactly the shape every real API sends. Read every pet name without a single crash and without a single if.
requirements:
- Create
users: an array of three objects —{ name: "Ada", pet: { name: "Rex" } },{ name: "Mo" }, and{ name: "Liv", pet: { name: "Tuna" } }. - Write
petLabel(user)returning the user’s pet’s name — or"no pet"when there isn’t one. Noifallowed: today’s two operators do it in one line. - Loop over
users(for...of) and print each label, in order.
when you press RUN, the console must show exactly:
✏️ Quick check 1
const volume = 0. What does console.log(volume ?? 50) print?
✏️ Quick check 2
user has no cat property. What does console.log(user.cat?.name) print?
✏️ Quick check 3
Which operator treats the empty string "" as missing and replaces it — || or ?? ?
🗣️ Now teach it back
Explain to a friend why user.cat.name crashes but user.cat?.name doesn’t, and why volume ?? 50 is safer than volume || 50 when volume is 0.
Write it as if your friend is sitting next to you. Saved to your journal — future-you will use these notes to teach others.