Chapter 25 of 28 · JavaScript for Beginners — Complete Free Tutorial
Browser Storage & Web APIs
Browsers give JavaScript memory beyond the session: localStorage for preferences that persist, sessionStorage for tab-scoped state, cookies for server-readable data — plus a family of Web APIs (clipboard, history, geolocation, media). This chapter covers choosing the right store, the JSON round-trip, quotas, and the APIs you will actually use.
Choosing a store
| Store | Lifetime | Sent to server | Typical use |
|---|---|---|---|
localStorage | Until cleared (years) | No | Preferences, drafts, onboarding state |
sessionStorage | Until the tab closes | No | Multi-step form state, tab-local data |
| Cookies | Set expiry | Yes — every request | Auth sessions, server-required data |
| IndexedDB | Until cleared | No | Large/structured data (offline apps) |
| Memory (variables) | Page lifetime | No | Everything else |
Size: ~5 MB per origin for Web Storage, ~4 KB per cookie. Storage is string-only for localStorage/sessionStorage — hence JSON.
localStorage basics
localStorage.setItem('theme', 'dark');
console.log(localStorage.getItem('theme')); // → 'dark'
console.log(localStorage.getItem('nope')); // → null (not undefined)
localStorage.removeItem('theme');
localStorage.setItem('visit_' + Date.now(), '1');
console.log(localStorage.length);
localStorage.clear(); // everything for this origin
Storing objects and arrays (JSON round-trip)
const cart = { items: [{ id: 1, qty: 2 }], updated: Date.now() };
localStorage.setItem('cart', JSON.stringify(cart));
const saved = JSON.parse(localStorage.getItem('cart') ?? '{}');
console.log(saved.items[0].id); // → 1
Strings only — so every write is JSON.stringify and every read is JSON.parse. Wrap reads in try/catch: a corrupted value throws.
A tiny safe wrapper (do this once, reuse forever)
export const store = {
get(key, fallback = null) {
try {
const raw = localStorage.getItem(key);
return raw === null ? fallback : JSON.parse(raw);
} catch {
return fallback; // corrupted entry → default
}
},
set(key, value) {
try {
localStorage.setItem(key, JSON.stringify(value));
return true;
} catch (err) {
console.error('storage full?', err); // QuotaExceededError
return false;
}
},
remove(key) { localStorage.removeItem(key); },
};
store.set('prefs', { lang: 'en', compact: true });
const prefs = store.get('prefs', { lang: 'en' });
Practical example: theme + todos that survive reloads
// theme
const apply = (mode) => {
document.documentElement.dataset.theme = mode; // drives CSS variables
store.set('theme', mode);
};
apply(store.get('theme', 'light'));
// todos
let todos = store.get('todos', []);
function addTodo(text) {
todos = [...todos, { id: crypto.randomUUID(), text, done: false }];
store.set('todos', todos);
render(todos);
}
Expected output: refresh or reopen the browser — theme and todos remain exactly as left.
Privacy and safety rules
- Never store secrets in JS-readable storage — any script (including injected third-party code) can read it; auth tokens belong in
HttpOnlycookies set by the server. - Never store what the server should authorise — hiding a price field client-side is not security.
- Purge on logout —
localStorageoutlives the session: clear user data on account switch. - Multi-tab? The
storageevent fires in other tabs — great for syncing theme across tabs.
Cookies in 30 seconds
document.cookie = 'visits=1; expires=Fri, 31 Dec 2027 23:59:59 GMT; path=/; SameSite=Lax';
console.log(document.cookie); // only non-HttpOnly cookies visible to JS
Features like HttpOnly (invisible to JS — XSS cannot steal it), Secure, SameSite and per-cookie size limits are why servers handle auth via cookies, not localStorage. Cookies ride along with every request — that is their superpower and their cost.
The Clipboard API
await navigator.clipboard.writeText('copied!');
const text = await navigator.clipboard.readText(); // needs permission + secure context
Requires HTTPS (or localhost) and often a user gesture — exactly the pattern behind every “Copy” button (this site's code-block Copy buttons included).
History API (client-side navigation)
history.pushState({ tab: 'settings' }, '', '/settings'); // URL changes, no reload
window.addEventListener('popstate', (e) => render(e.state?.tab ?? 'home'));
This is the foundation of single-page-app routers. Note this site's module-based architecture still uses real server URLs — pushState is for apps that own their routing entirely.
Other APIs worth knowing
| API | One-liner |
|---|---|
IntersectionObserver | “Is it on screen?” — lazy images, scroll reveals (no scroll-handler jank) |
ResizeObserver | React to element size changes |
Geolocation | navigator.geolocation.getCurrentPosition(cb) (permission prompt) |
matchMedia | JS equivalent of media queries: matchMedia('(prefers-color-scheme: dark)') |
navigator.onLine | Online/offline events for connection-aware UI |
Notification | System notifications (permission required) |
Common mistakes
- Storing objects without JSON — you save
[object Object]and parse garbage. - Un-guarded
JSON.parse— one corrupted entry crashes app start; use the wrapper above. - Private data in
localStorage— XSS reads it freely; server enforces authority anyway. - Storage quota ignored —
QuotaExceededErrorafter ~5 MB; catch and inform the user. - Sync storage reads in hot loops —
getItemis synchronous I/O; cache in memory and write once (debounced). - Forgetting HTTPS — clipboard, service workers and many APIs are unavailable on insecure origins.
Practice checklist
- Persist a to-do list through reload with the
storewrapper; verify with dev tools → Application → Local Storage. - Build a two-tab theme switcher and sync with the
storageevent. - Add a “Copy link” button using
navigator.clipboard.writeTextwith a fallback message. - Demonstrate quota handling: catch a simulated
QuotaExceededError.
One precision tool remains for text processing — next: Regular Expressions in JavaScript.
Frequently Asked Questions
JavaScript is the programming language of the web. It adds behaviour to pages: responding to clicks and input, validating forms, fetching data from APIs, updating the DOM without reloading, and much more. It also runs on servers (Node.js), in desktop and mobile apps, and in build tooling.
They are unrelated languages that share a similar name for historical marketing reasons. Java is a statically-typed compiled language; JavaScript is a dynamically-typed interpreted language designed for the browser. Knowing one does not imply knowledge of the other.
Helpful but not mandatory — HTML gives you the structure JavaScript manipulates and CSS the styling it toggles. If you know basic tags, classes and selectors, you have enough to start; the DOM chapters assume only that much.
Use const by default and let when the value must change. Avoid var entirely in modern code: it is function-scoped instead of block-scoped, hoists differently, and can create accidental globals. This course uses const and let throughout.
The Document Object Model is the browser’s in-memory tree of objects representing your HTML. JavaScript reads and modifies that tree — selecting elements, changing text, adding nodes, attaching event listeners — and the browser re-renders the page to match.
== compares after converting types (so 5 == "5" is true), while === requires the same value AND the same type (5 === "5" is false). Loose equality has famously confusing rules — use === and !== everywhere unless you have a specific reason not to.
JavaScript is single-threaded, so long-running work (timers, fetches, events) is handled by the browser and its callbacks are queued. The event loop moves queued callbacks onto the call stack only when it is empty, with promise callbacks (microtasks) taking priority over timers. It is why asynchronous code never freezes the page.
A Promise is an object representing a future value — pending, fulfilled or rejected — that can be chained and combined (Promise.all). async/await is syntax sugar over promises that lets asynchronous code read top-to-bottom with ordinary try/catch error handling.
Yes. Frameworks are built on exactly these fundamentals — components are functions, state is your variable discipline, effects are events, rendering is DOM work. Developers who learn vanilla first understand what the framework is doing and debug far faster.
Type out the examples instead of copying, build small end-to-end projects (expense tracker, weather app, quiz), handle every UI state (loading, empty, success, error), and revisit fundamentals while reading real code. Consistency beats volume — a little most days outpaces occasional marathons.