Free Handbook · Runs in your browser

Advanced JavaScript

Destructuring and spread in depth, iterators and generators traced, immutability and copying, regular expressions that you will actually use, dates without the footguns, closures in practice, and the Symbol / WeakMap / Proxy corner of the language.

0 / 111 lessons🔥 0 day streak
ShareXLinkedIn

Module 09 · what you'll be able to do

  • Destructure and spread anything, including nested and with defaults
  • Write iterators and generators and know when lazy sequences pay off
  • Update data immutably and know shallow from deep copies
  • Match, extract and replace with regular expressions
  • Handle dates and time zones without losing a day
  • Recognise Symbol, WeakMap and Proxy when you meet them in a library
01

Destructuring and spread, in depth

javascriptEdit it. ⌘/Ctrl + Enter runs.
You should see
200 Ada admin [ 'dev' ] 1 20
1 Ada x
2 Bob none
[ 'Ada', 'Bob' ]
2 1 20 [ 30, 40 ]
{ id: 7, name: 'Grace', roles: [ 'admin', 'dev', 'ops' ] } Ada
{ a: 1, b: 2, c: 3 }
[ 3, 1 ] [ 'h', 'e', 'y' ] 9
7 { name: 'Ada', roles: [ 'admin', 'dev' ] }
2 9
Your turn
Write pick(obj, ...keys) and omit(obj, ...keys) using Object.entries, filter and Object.fromEntries.
Error you will hit

TypeError: Cannot destructure property 'name' of 'undefined'

javascript
function greet({ name }) { return `hi ${name}` }
greet()
Uncaught TypeError: Cannot destructure property 'name' of 'undefined' as it is undefined.
    at greet (your code:1)
Why the engine said that

Destructuring reads properties, and you cannot read a property of undefined. The caller passed nothing.

The fix

Give the whole parameter a default: function greet({ name = "friend" } = {}). The = {} handles the missing argument; name = … handles the missing field.

javascript
function greet({ name = "friend" } = {}) { return `hi ${name}` }
console.log(greet(), greet({ name: "Ada" }))
02

Iterators and generators

Anything with a [Symbol.iterator]() method that returns an object with next() is iterable: for…of, spread, destructuring and Array.from all use that protocol. A generator (function*) writes an iterator for you: each yield hands out one value and pauses until the next one is asked for. That laziness is the point — infinite sequences, huge files, and pipelines that do no work until consumed.

javascriptEdit it. ⌘/Ctrl + Enter runs.
You should see
[ 3, 2, 1 ]
[ 3, 2, 1 ]
created, nothing ran yet
  start
{ value: 1, done: false }
  between
{ value: 2, done: false }
  end
{ value: 'done', done: true }
{ value: undefined, done: true }
[ 1, 4, 9, 16, 25 ]
10 15
iterator helpers available
Your turn
Write function* chunks(arr, size) that yields arr in slices of size. Then [...chunks([1,2,3,4,5], 2)].
VisualizeA generator pausing at each yieldStep 1 / 4
function* gen() {
yield "a"
yield "b"
}
const it = gen()
console.log(it.next().value)
console.log(it.next().value)
console.log(it.next().done)
Line 5

Calling a generator function runs NONE of its body. It returns an iterator object, paused before line 2.

Variables now
itpaused at start
All 4 steps as a table
StepLineWhat happenedVariables now
15Calling a generator function runs NONE of its body. It returns an iterator object, paused before line 2.it = paused at start
26next() runs the body until the first yield, which hands out "a" and pauses there.it = paused at line 2
37Resume from where it paused; run to the next yield.it = paused at line 3
48Resume; the body ends. done is true and value is undefined (or the return value).it = finished
03

Immutability and copying

Mutation is the source of most "it changed and I don't know why" bugs, and React, Redux and every diffing algorithm require you not to. The habit: make a new value instead of changing the old one. Spread and the non-mutating array methods do one level; structuredClone does all levels; Object.freeze catches accidental writes in development.

javascriptEdit it. ⌘/Ctrl + Enter runs.
You should see
dark light
true false
[ 3, 1, 2 ] [ 1, 2, 3 ] [ 3, 1, 2, 4 ] [ 3, 2 ] [ 9, 1, 2 ]
Bob
Bob Cy
TypeError
3000 false true
true

The "use strict" inside the try block is a directive only at the top of a file or function, so in a real sloppy script that assignment is silently ignored; in a module it throws exactly as shown.

Mid-levelWhy do React and Redux insist on immutable updates?

They detect change by reference: prev !== next is O(1), while comparing contents is O(n). If you mutate, the reference is the same and nothing re-renders. Immutable updates also give you free history (undo, time-travel debugging) and make concurrent rendering safe, because an in-flight render never sees a half-updated object.

04

Regular expressions

A regex is a pattern for matching text. You will use a small subset constantly — validation, extraction, cleanup — and reach for a reference for the rest. The five methods: test (boolean), match / matchAll (extract), replace (transform), split (tokenise). Named groups make extraction readable; the g flag means "all occurrences".

javascriptEdit it. ⌘/Ctrl + Enter runs.
You should see
true false
true false
2026 09 2026-09-20 0
[ '1', '22', '333' ]
x → 1
y → 22
20/09/2026
Hello World
too many spaces
42.50
[ 'a', 'b', 'c', 'd' ]
true [ 'line1', 'line2' ]
true false
hello-world-2026
Your turn
Write parseLog(line) for lines like "2026-09-20 14:03:22 ERROR db timeout" returning { date, time, level, message } with named groups.
PatternMatchesPatternMatches
.any char (not newline)\d \w \sdigit, word char, whitespace (uppercase = NOT)
^ $start, end\bword boundary
* + ?0+, 1+, 0–1{n} {n,} {n,m}exact / at least / between
[abc] [^abc] [a-z]set, negated set, rangea|beither
(x) (?<name>x)capture, named capture(?:x)group without capturing
*? +?lazy (shortest match)(?=x) (?!x)lookahead: followed / not followed by
Two regex traps
Greedy by default: /<.+>/ on "<a><b>" matches the whole thing; use .+?. Catastrophic backtracking: nested quantifiers like (a+)+$ can take exponential time on the wrong input — a denial-of-service vector if the input is user-controlled. Keep patterns simple and anchored.
05

Dates and time

The built-in Date is a millisecond timestamp with a confusing API: months are 0-based, it silently rolls over invalid dates, and it always uses the local time zone for its getters. Two rules keep you safe: store and transmit UTC (ISO strings or epoch milliseconds), and format for display with Intl. For anything involving time zones, recurrence or arithmetic, use the upcoming Temporal API or a library (date-fns, Day.js).

javascriptEdit it. ⌘/Ctrl + Enter runs.
You should see
1970-01-01T00:00:00.000Z true
2026 9 20 0
2026-09-27 7 days
2026-03-03
true Invalid Date
20 September 2026 at 16:00
yesterday
number true
elapsed ms is a number: number
Your turn
Write daysUntil(isoDate) using UTC midnight for both dates so the answer does not depend on the time of day.
Error you will hit

Off by one day: new Date("2026-09-20")

javascript
const d = new Date("2026-09-20")
console.log(d.getDate())        // 19 in New York, 20 in London and Mumbai
19   // on a machine west of UTC
Why the engine said that

A date-only ISO string is parsed as UTC midnight, but getDate() reports in local time — which in the Americas is still the evening before. Same value, two calendars.

The fix

Read with the UTC getters (getUTCDate) or format with Intl and an explicit timeZone. For calendar dates with no time, keep them as strings ("2026-09-20") until you must compute.

06

Closures in practice: debounce, memoize, curry

javascriptEdit it. ⌘/Ctrl + Enter runs.
You should see
3 3 5 computed: 2
24 24 24
Hello, Ada Hello, Bob
[email protected]
[ 'a', 'b' ] undefined
ran 1 undefined undefined
07

Symbol, WeakMap and Proxy

Three features you will rarely write but will meet in libraries. Symbol: a unique key that cannot collide and does not show up in Object.keys — and the "well-known" symbols (Symbol.iterator, Symbol.toPrimitive) let objects hook into language behaviour. WeakMap: keys are objects and are held weakly, so metadata attached to an object disappears with it — no memory leak. Proxy: intercept property reads, writes and calls; how Vue reactivity and validation libraries work.

javascriptEdit it. ⌘/Ctrl + Enter runs.
You should see
42 [ 'name' ] {"name":"Ada"} Symbol(id) false
$19.99 39.98 20.99
{ clicks: 1 } false
36 <no missing>
age must be an integer
true 2
SeniorExplain how a reactive framework like Vue knows to re-render when you set state.age = 37.

The state object is wrapped in a Proxy. The get trap records which effect (component render, computed value) read which property — dependency tracking. The set trap looks up the effects that depend on that property and schedules them to re-run. Because it is a Proxy, ordinary property syntax works and nested objects are wrapped lazily on first access. The trade-off is that identity changes (proxy !== raw) and that some built-ins (Map, Date) need special handling.

Finish the JavaScript handbook, then get hired

Sit the exam for your certificate, run your resume through the ATS checker, and see the jobs that ask for exactly this.

Check my resume
Found this course useful? Share it.
ShareXLinkedIn

Comments

0

Join the conversation. Sign in to leave a comment — we'd love to hear your thoughts.