Lesson 0.8 · JavaScript in general
Modules and built-in tools
import/export, JSON, Math, Date, Intl, localStorage and classes in a nutshell.
Loading lesson…
Lesson 0.8 · JavaScript in general
import/export, JSON, Math, Date, Intl, localStorage and classes in a nutshell.
Loading lesson…
A big project is like a workshop. If all the tools lay in one heap, you'd never find anything. So each kind has its own labelled drawer – and when you need a screwdriver, you open the "screwdrivers" drawer. In JavaScript the drawers are modules: every file keeps its things to itself and only "lends out" what it explicitly exports.
The second half of the lesson is a tour of the tools JavaScript comes with: JSON, Math, Date, Intl, localStorage and classes. You don't need to know them by heart – it's enough to know they exist and how to find them.
Every file is a separate module. The variables and functions in it are private until you export them. There are two kinds of export:
// math.jsexport const PI = 3.14159 // a named export (there can be many)export function add(a, b) { return a + b }function secret() { … } // without export – not reachable from outsideexport default function multiply(a, b) { // the default export (only one per file)return a * b}
// app.jsimport multiply, { PI, add } from './math.js' // the default without braces, named ones in { }import times from './math.js' // you can name the default whatever you likeimport { add as plus } from './math.js' // rename a named one with asimport * as math from './math.js' // everything under one name: math.add, math.PI
| Named export | Default export | |
|---|---|---|
| How many per file | any number | one at most |
| Import | import { add } from './math' | import multiply from './math' |
| Name when importing | must match (or as) | your choice |
| Typically in React | helper functions, hooks, types | the file's component (e.g. Lesson.tsx) |
import { useState } from 'react' // no dot = a package from node_modulesimport { products } from '../course/fakeApi.en' // ./ and ../ = your own file, path relative to this oneimport './styles.css' // an import for its side effect (loads the styles)// The .js/.ts/.tsx extension is usually left out in Vite projects
// components/index.js – one entry point for the whole folderexport { Button } from './Button'export { Card } from './Card'// elsewhere then: import { Button, Card } from './components'// Dynamic import – loads the module only when it's needed (returns a Promise)const { heavyChart } = await import('./charts.js')
JSON is a text notation for data that looks almost like a JavaScript object. It's used everywhere data travels: between the server and the browser, into files, into localStorage.
const user = { name: 'Anna', age: 31, tags: ['admin'] }const text = JSON.stringify(user)// '{"name":"Anna","age":31,"tags":["admin"]}' – one long piece of TEXTJSON.stringify(user, null, 2) // nicely indented – for debuggingJSON.parse(text) // back to an object (a new object!)
Math.round(4.5) // 5Math.floor(4.9) // 4 – downMath.ceil(4.1) // 5 – upMath.trunc(-4.9) // -4 – cut off the decimal partMath.abs(-7) // 7Math.max(3, 8, 1) // 8Math.min(...[3, 8, 1]) // 1 – with an array via spreadMath.sqrt(16) // 42 ** 8 // 256 – power (formerly Math.pow)// A random whole number from min to max (inclusive)const randomInt = (min, max) => Math.floor(Math.random() * (max - min + 1)) + minrandomInt(1, 6) // a die// A random item of an arrayconst pick = (arr) => arr[Math.floor(Math.random() * arr.length)]// Limiting to a rangeconst clamp = (n, min, max) => Math.min(Math.max(n, min), max)clamp(150, 0, 100) // 100
const now = new Date() // nowconst d = new Date(2026, 9, 4) // October 4, 2026 – months count from 0! 😱const iso = new Date('2026-10-04') // from text in the ISO format (year-month-day)const fromMs = new Date(1_700_000_000_000) // from the number of milliseconds since 1 January 1970d.getFullYear() // 2026d.getMonth() // 9 – October (January = 0)d.getDate() // 4 – the day of the monthd.getDay() // 0–6 – the day of the week (Sunday = 0)d.getHours() // 0Date.now() // the current time in ms – quick measuring and unique numbersd.getTime() // this date in msd.toISOString() // the standard text for servers – in UTC, so it can show a different day than your local time
// The difference of two dates = the difference in millisecondsconst DAY = 24 * 60 * 60 * 1000const days = Math.round((new Date('2026-12-24') - new Date('2026-10-04')) / DAY) // 81// ComparisondateA < dateB // works (compares ms)dateA.getTime() === dateB.getTime() // equality – === on two Date objects is always false!// Adding days – setDate CHANGES the object, so make a copy firstconst tomorrow = new Date(today)tomorrow.setDate(tomorrow.getDate() + 1) // Date handles overflowing the end of the month itself
Formatting numbers and dates by hand (thousands separators, the decimal mark, the currency symbol, month names) is unnecessary. The browser can do it in every language:
const n = 1234567.891n.toLocaleString('en-US') // '1,234,567.891'n.toLocaleString('de-DE') // '1.234.567,891'new Intl.NumberFormat('en-US', { style: 'currency', currency: 'USD' }).format(n)// '$1,234,567.89'new Intl.NumberFormat('en-US', { maximumFractionDigits: 1 }).format(3.14159) // '3.1'new Intl.NumberFormat('en-US', { style: 'percent' }).format(0.256) // '26%'const date = new Date(2026, 9, 4)date.toLocaleDateString('en-US') // '10/4/2026'date.toLocaleDateString('en-GB') // '04/10/2026'date.toLocaleDateString('en-US', { day: 'numeric', month: 'long' }) // 'October 4'date.toLocaleTimeString('en-US', { hour: '2-digit', minute: '2-digit' }) // '12:00 AM'new Intl.RelativeTimeFormat('en', { numeric: 'auto' }).format(-1, 'day') // 'yesterday'new Intl.ListFormat('en').format(['Anna', 'Peter', 'Eve']) // 'Anna, Peter, and Eve'
What you see: Fields for a number and a date. Below them, how the built-in methods and Intl format them.
Try it:
toFixed and toLocaleString('en-US'): the first gives no thousands separators, the second formats it for people.RelativeTimeFormat writes "yesterday" / "tomorrow".The takeaway: Format numbers and dates for people with Intl and toLocale… plus a locale. Building the text by hand is unnecessary and error-prone.
Loading the interactive part…
localStorage is a small store in the browser. Data in it survive reloading the page and even closing the browser. The course uses it to remember which challenges you've finished.
localStorage.setItem('theme', 'dark') // store (key, value)localStorage.getItem('theme') // 'dark'localStorage.getItem('doesNotExist') // nulllocalStorage.removeItem('theme') // delete// It can only store TEXT – objects via JSONconst settings = { theme: 'dark', fontSize: 16 }localStorage.setItem('settings', JSON.stringify(settings))const loaded = JSON.parse(localStorage.getItem('settings') ?? 'null')// ❌ Without JSONlocalStorage.setItem('settings', settings) // stores '[object Object]'
A class is a cake mould: it describes what every object you make with it (new) looks like and what it can do. In modern React you'll hardly meet them – components are written as functions. But they do turn up now and then (the error boundary in lesson 10, libraries, older code), so you should recognise them.
class Counter {count = 0 // a property with a default valueconstructor(start) { // called on new Counter(…)this.count = start ?? 0 // this = the object being made right now}increment() { // a methodthis.count++return this.count}}const a = new Counter(10)a.increment() // 11a.count // 11class LimitedCounter extends Counter { // inheritance: everything from Counter + something extraincrement() {if (this.count >= 3) return this.countreturn super.increment() // call the parent class's version}}
Two built-in functions: one from the Math object, the other from JSON.
CHALLENGE: Round up and store as text
1. roundUp(4.2) should return 5 – rounding up (Math.ceil).
2. toJson({ a: 1 }) should return the text '{"a":1}' (JSON.stringify).Loading the interactive part…
Seven helper functions you'll find in almost every project: formatting a price and a date, limiting to a range, a random number, moving a date and saving to localStorage.
CHALLENGE: The toolbox drawer (Intl, Math, Date, JSON, localStorage)
Write the bodies of seven helper functions that almost every app has.
The tests below run on their own and show ✅/❌.
1. formatPrice(1234.5) → '$1,234.50'
new Intl.NumberFormat('en-US', { style: 'currency', currency: 'USD' }).format(…)
2. formatDate(new Date(2026, 9, 4)) → '10/4/2026' (careful: months in Date count from 0!)
date.toLocaleDateString('en-US')
3. clamp(n, min, max) – return n, but not less than min and not more than max.
clamp(15, 0, 10) → 10, clamp(-3, 0, 10) → 0. Math.min and Math.max.
4. randomInt(min, max) – a random WHOLE number from min to max, BOTH included.
Math.floor(Math.random() * (max - min + 1)) + min
5. addDays(date, days) – return a NEW date moved by days days. Don't change the original date!
const copy = new Date(date); copy.setDate(copy.getDate() + days)
6. saveJSON(key, value) – store any value in localStorage (JSON.stringify).
7. loadJSON(key, fallback) – load a value from localStorage (JSON.parse). When there's nothing there
(getItem returns null) or the stored text is invalid JSON, return fallback (try/catch).Loading the interactive part…
{ }, the default without them. A path with ./ is your own file, without it a package.Math.floor(Math.random() * (max - min + 1)) + min.setDate changes the object.toLocale… with a locale format numbers, currencies and dates for any language.new, this, extends). In modern React you'll rarely meet them.Checks, Check)Checks, Check – from the file src/course/checks.tsx. The course helper for function-style challenges: runs the tests and shows ✅/❌. You don't need to know how it works inside – it just tells you which steps are done./** One test: a description and a function returning true (passed) or false. It may be async. */export type Check = [name: string, test: () => boolean | Promise<boolean>]/*** Runs the tests of a challenge and shows ✅ or ❌ for each.* An error thrown in a test (e.g. when trying to change a frozen object) is shown under the test.*/export function Checks({ tests }: { tests: Check[] }) {const t = useT()const [results, setResults] = useState<Result[]>(() => tests.map(() => ({ status: 'pending' })))useEffect(() => {let ignore = falsetests.forEach(([, test], i) => {Promise.resolve().then(test).then((ok): Result => (ok ? { status: 'ok' } : { status: 'fail' }),(e): Result => ({ status: 'fail', error: e instanceof Error ? e.message : String(e) }),).then((result) => {if (ignore) returnsetResults((prev) => prev.map((r, idx) => (idx === i ? result : r)))})})return () => {ignore = true}}, [tests])const passed = results.filter((r) => r.status === 'ok').lengthreturn (<div className="stack"><strong className={passed === tests.length ? 'ok' : undefined}>{t.checksPassed(passed, tests.length)}</strong><ul className="list">{tests.map(([name], i) => {const r = results[i] ?? { status: 'pending' }return (<li key={name}>{r.status === 'pending' ? '⏳' : r.status === 'ok' ? '✅' : '❌'} {name}{r.status === 'fail' && r.error && (<div className="bad muted">{t.checksError} {r.error}</div>)}</li>)})}</ul></div>)}
Checks, Check)Checks, Check – from the file src/course/checks.tsx. The course helper for function-style challenges: runs the tests and shows ✅/❌. You don't need to know how it works inside – it just tells you which steps are done./** One test: a description and a function returning true (passed) or false. It may be async. */export type Check = [name: string, test: () => boolean | Promise<boolean>]/*** Runs the tests of a challenge and shows ✅ or ❌ for each.* An error thrown in a test (e.g. when trying to change a frozen object) is shown under the test.*/export function Checks({ tests }: { tests: Check[] }) {const t = useT()const [results, setResults] = useState<Result[]>(() => tests.map(() => ({ status: 'pending' })))useEffect(() => {let ignore = falsetests.forEach(([, test], i) => {Promise.resolve().then(test).then((ok): Result => (ok ? { status: 'ok' } : { status: 'fail' }),(e): Result => ({ status: 'fail', error: e instanceof Error ? e.message : String(e) }),).then((result) => {if (ignore) returnsetResults((prev) => prev.map((r, idx) => (idx === i ? result : r)))})})return () => {ignore = true}}, [tests])const passed = results.filter((r) => r.status === 'ok').lengthreturn (<div className="stack"><strong className={passed === tests.length ? 'ok' : undefined}>{t.checksPassed(passed, tests.length)}</strong><ul className="list">{tests.map(([name], i) => {const r = results[i] ?? { status: 'pending' }return (<li key={name}>{r.status === 'pending' ? '⏳' : r.status === 'ok' ? '✅' : '❌'} {name}{r.status === 'fail' && r.error && (<div className="bad muted">{t.checksError} {r.error}</div>)}</li>)})}</ul></div>)}