HTML5 APIs - Advanced Features
1. Web Storage API (localStorage, sessionStorage)
| Storage Type | Scope | Lifetime | Capacity | Use Cases |
|---|---|---|---|---|
| localStorage | Origin (protocol + domain + port) | Permanent (until cleared) | ~5-10MB per origin | User preferences, app state, cached data |
| sessionStorage | Tab/window per origin | Until tab/window closed | ~5-10MB per origin | Form data, session state, temp data |
| Cookies | Origin (+ path config) | Configurable expiry | ~4KB per cookie | Auth tokens, tracking, server access |
| Method/Property | Syntax | Description | Returns |
|---|---|---|---|
| setItem() | storage.setItem(key, value) |
Store key-value pair (strings only) | undefined |
| getItem() | storage.getItem(key) |
Retrieve value by key | string | null |
| removeItem() | storage.removeItem(key) |
Delete specific key-value pair | undefined |
| clear() | storage.clear() |
Remove all items from storage | undefined |
| key() | storage.key(index) |
Get key name at index position | string | null |
| length | storage.length |
Number of stored items | number |
Example: Web Storage operations with JSON serialization
// Store simple values (localStorage)
localStorage.setItem('username', 'john_doe');
localStorage.setItem('theme', 'dark');
// Retrieve values
const username = localStorage.getItem('username'); // "john_doe"
const theme = localStorage.getItem('theme'); // "dark"
// Store complex objects (must stringify)
const user = {
id: 123,
name: 'John Doe',
preferences: { theme: 'dark', lang: 'en' }
};
localStorage.setItem('user', JSON.stringify(user));
// Retrieve and parse objects
const storedUser = JSON.parse(localStorage.getItem('user'));
console.log(storedUser.preferences.theme); // "dark"
// Remove specific item
localStorage.removeItem('theme');
// Clear all storage
localStorage.clear();
// sessionStorage (same API, different lifetime)
sessionStorage.setItem('tempData', 'session-only');
// Iterate through all items
for (let i = 0; i < localStorage.length; i++) {
const key = localStorage.key(i);
const value = localStorage.getItem(key);
console.log(key, value);
}
// Storage event listener (fires on other tabs/windows)
window.addEventListener('storage', (e) => {
console.log('Key:', e.key);
console.log('Old value:', e.oldValue);
console.log('New value:', e.newValue);
console.log('URL:', e.url);
console.log('Storage area:', e.storageArea);
});
Security Warning: Never store sensitive data (passwords, credit cards, tokens) in
localStorage/sessionStorage - it's accessible via JavaScript and vulnerable to XSS attacks. Data is stored as
plain text. Use httpOnly cookies or secure server-side sessions for sensitive information.
Best Practices: Always use try-catch for storage operations (can fail if quota exceeded or
disabled). Use JSON for objects. Implement versioning for data schema changes. Clear old data periodically.
Consider IndexedDB for large datasets (>10MB). Use compression for large strings.
2. Geolocation API Integration (navigator.geolocation)
| Method | Syntax | Description | Requires Permission |
|---|---|---|---|
| getCurrentPosition() | navigator.geolocation.getCurrentPosition(success, error, options) |
Get one-time location | ✅ Yes |
| watchPosition() | navigator.geolocation.watchPosition(success, error, options) |
Monitor location changes | ✅ Yes |
| clearWatch() | navigator.geolocation.clearWatch(watchId) |
Stop monitoring location | ❌ No |
| Position Property | Type | Description | Always Available |
|---|---|---|---|
| coords.latitude | number |
Latitude in decimal degrees (-90 to 90) | ✅ Yes |
| coords.longitude | number |
Longitude in decimal degrees (-180 to 180) | ✅ Yes |
| coords.accuracy | number |
Accuracy in meters | ✅ Yes |
| coords.altitude | number | null |
Height above sea level in meters | ❌ No (GPS only) |
| coords.altitudeAccuracy | number | null |
Altitude accuracy in meters | ❌ No |
| coords.heading | number | null |
Direction in degrees (0-360, 0=North) | ❌ No (moving only) |
| coords.speed | number | null |
Speed in meters per second | ❌ No (moving only) |
| timestamp | DOMTimeStamp |
Time when position was acquired | ✅ Yes |
| Options Property | Type | Default | Description |
|---|---|---|---|
| enableHighAccuracy | boolean |
false |
Use GPS (slower, more battery, accurate) |
| timeout | number |
Infinity |
Max wait time in milliseconds |
| maximumAge | number |
0 |
Accept cached position (milliseconds old) |
Example: Geolocation with error handling and options
// Check if geolocation is supported
if ('geolocation' in navigator) {
// Get current position (one-time)
navigator.geolocation.getCurrentPosition(
// Success callback
(position) => {
const lat = position.coords.latitude;
const lng = position.coords.longitude;
const accuracy = position.coords.accuracy;
console.log(`Location: ${lat}, ${lng}`);
console.log(`Accuracy: ${accuracy} meters`);
// Use coordinates (e.g., show on map)
showOnMap(lat, lng);
},
// Error callback
(error) => {
switch(error.code) {
case error.PERMISSION_DENIED:
console.error('User denied geolocation');
break;
case error.POSITION_UNAVAILABLE:
console.error('Location unavailable');
break;
case error.TIMEOUT:
console.error('Request timeout');
break;
default:
console.error('Unknown error');
}
},
// Options
{
enableHighAccuracy: true, // Use GPS
timeout: 5000, // 5 second timeout
maximumAge: 60000 // Accept 1-minute-old cache
}
);
// Watch position (continuous monitoring)
const watchId = navigator.geolocation.watchPosition(
(position) => {
updateMap(position.coords.latitude, position.coords.longitude);
// Check if device is moving
if (position.coords.speed !== null) {
console.log(`Speed: ${position.coords.speed} m/s`);
}
if (position.coords.heading !== null) {
console.log(`Heading: ${position.coords.heading}°`);
}
},
(error) => console.error(error),
{ enableHighAccuracy: true }
);
// Stop watching after 10 seconds
setTimeout(() => {
navigator.geolocation.clearWatch(watchId);
console.log('Stopped watching position');
}, 10000);
} else {
console.error('Geolocation not supported');
}
Browser Support: All modern browsers - Requires HTTPS (except
localhost). Mobile devices generally more accurate than desktops. User must grant permission. GPS
(enableHighAccuracy) drains battery significantly.
3. File API and File Handling (FileReader)
| Interface | Purpose | Key Properties/Methods |
|---|---|---|
| File | Represents a file | name, size, type, lastModified |
| FileList | Collection of File objects | length, indexed access |
| FileReader | Read file contents asynchronously | readAsText(), readAsDataURL(), readAsArrayBuffer() |
| Blob | Raw binary data | size, type, slice(), text() |
| URL.createObjectURL() | Create temporary URL for file/blob | Returns blob: URL for preview/download |
| FileReader Method | Result Format | Use Case |
|---|---|---|
| readAsText(file, encoding) | Plain text string | Read text/JSON/CSV files |
| readAsDataURL(file) | Base64 data URL | Image previews, inline embedding |
| readAsArrayBuffer(file) | ArrayBuffer (binary) | Binary processing, encryption, WebGL |
| readAsBinaryString(file) | Binary string DEPRECATED | Use readAsArrayBuffer instead |
| FileReader Event | When Fired | Event Data |
|---|---|---|
| loadstart | Reading starts | ProgressEvent |
| progress | During reading (periodic) | loaded, total bytes |
| load | Reading completed successfully | target.result contains data |
| error | Reading failed | target.error |
| loadend | Reading finished (success or failure) | Always fires after load/error |
| abort | Reading cancelled via abort() | AbortEvent |
Example: File input with preview and validation
<input type="file" id="fileInput" multiple accept="image/*">
<div id="preview"></div>
<script>
const fileInput = document.getElementById('fileInput');
const preview = document.getElementById('preview');
fileInput.addEventListener('change', (e) => {
const files = e.target.files; // FileList
// Validate and process each file
Array.from(files).forEach(file => {
// File properties
console.log('Name:', file.name);
console.log('Size:', file.size, 'bytes');
console.log('Type:', file.type);
console.log('Modified:', new Date(file.lastModified));
// Validation
const maxSize = 5 * 1024 * 1024; // 5MB
if (file.size > maxSize) {
alert(`${file.name} is too large`);
return;
}
if (!file.type.startsWith('image/')) {
alert(`${file.name} is not an image`);
return;
}
// Method 1: FileReader for preview
const reader = new FileReader();
reader.onload = (e) => {
const img = document.createElement('img');
img.src = e.target.result; // Data URL
img.style.maxWidth = '200px';
preview.appendChild(img);
};
reader.onerror = () => {
console.error('Error reading file');
};
reader.onprogress = (e) => {
if (e.lengthComputable) {
const percent = (e.loaded / e.total) * 100;
console.log(`Loading: ${percent.toFixed(0)}%`);
}
};
reader.readAsDataURL(file); // Start reading
// Method 2: Object URL (more efficient for large files)
const objectURL = URL.createObjectURL(file);
const img2 = document.createElement('img');
img2.src = objectURL;
preview.appendChild(img2);
// Important: Revoke object URL when done to free memory
img2.onload = () => URL.revokeObjectURL(objectURL);
});
});
</script>
Example: Read text file and parse JSON
<input type="file" id="jsonFile" accept=".json,.txt">
<script>
document.getElementById('jsonFile').addEventListener('change', (e) => {
const file = e.target.files[0];
if (file) {
const reader = new FileReader();
reader.onload = (e) => {
try {
// Parse JSON content
const data = JSON.parse(e.target.result);
console.log('Parsed JSON:', data);
} catch (error) {
console.error('Invalid JSON:', error);
}
};
reader.readAsText(file, 'UTF-8');
}
});
// Alternative: Modern Promise-based approach
async function readFileAsync(file) {
try {
const text = await file.text(); // Built-in method
const data = JSON.parse(text);
return data;
} catch (error) {
console.error('Error:', error);
}
}
</script>
Example: Drag and drop file upload
<div id="dropZone" style="border: 2px dashed #ccc; padding: 50px;">
Drop files here
</div>
<script>
const dropZone = document.getElementById('dropZone');
dropZone.addEventListener('dragover', (e) => {
e.preventDefault();
dropZone.style.background = '#e3f2fd';
});
dropZone.addEventListener('dragleave', () => {
dropZone.style.background = '';
});
dropZone.addEventListener('drop', (e) => {
e.preventDefault();
dropZone.style.background = '';
const files = e.dataTransfer.files;
// Process dropped files
Array.from(files).forEach(file => {
console.log('Dropped:', file.name);
// Upload to server
uploadFile(file);
});
});
function uploadFile(file) {
const formData = new FormData();
formData.append('file', file);
fetch('/upload', {
method: 'POST',
body: formData
})
.then(response => response.json())
.then(data => console.log('Uploaded:', data))
.catch(error => console.error('Upload failed:', error));
}
</script>
Security Note: Always validate file type and size on both client and server. Never trust file
extensions - check MIME type and magic numbers. Sanitize file names before saving. Use virus scanning for user
uploads. Limit file sizes to prevent DoS attacks.
4. History API (pushState, replaceState) and Navigation Management
| Method | Syntax | Description | Updates URL |
|---|---|---|---|
| pushState() | history.pushState(state, title, url) |
Add new history entry (creates new entry in stack) | ✅ Yes |
| replaceState() | history.replaceState(state, title, url) |
Modify current history entry (no new entry) | ✅ Yes |
| back() | history.back() |
Navigate to previous page (like browser back) | ✅ Yes |
| forward() | history.forward() |
Navigate to next page (like browser forward) | ✅ Yes |
| go() | history.go(delta) |
Navigate by offset (-1 = back, 1 = forward, 0 = reload) | ✅ Yes |
| Property | Type | Description | Writable |
|---|---|---|---|
| history.state | any |
State object of current history entry | ❌ Read-only |
| history.length | number |
Number of entries in session history | ❌ Read-only |
| history.scrollRestoration | 'auto' | 'manual' |
Control scroll position restoration on navigation | ✅ Yes |
| Event | When Fired | Event Properties |
|---|---|---|
| popstate | User navigates (back/forward), or history.back/forward/go() called | event.state contains state object |
| hashchange | URL hash (#) changes | oldURL, newURL |
Example: Single Page Application (SPA) routing with History API
// SPA Router implementation
const routes = {
'/': renderHome,
'/about': renderAbout,
'/contact': renderContact
};
// Navigate to new page without reload
function navigateTo(path) {
// Add to history
history.pushState({ path }, '', path);
// Render new content
renderPage(path);
}
// Render page based on path
function renderPage(path) {
const render = routes[path] || render404;
render();
// Update active nav links
document.querySelectorAll('nav a').forEach(link => {
link.classList.toggle('active', link.pathname === path);
});
}
// Handle browser back/forward
window.addEventListener('popstate', (e) => {
const path = e.state?.path || '/';
renderPage(path);
});
// Intercept link clicks
document.addEventListener('click', (e) => {
if (e.target.matches('a[href^="/"]')) {
e.preventDefault();
navigateTo(e.target.pathname);
}
});
// Example: Update URL without navigation
function updateFilters(filters) {
const url = new URL(window.location);
url.searchParams.set('filter', filters);
// Replace current history entry (no new back button entry)
history.replaceState({ filters }, '', url);
}
// Example: Handle state changes
window.addEventListener('popstate', (e) => {
console.log('State:', e.state);
console.log('URL:', location.pathname);
if (e.state?.filters) {
applyFilters(e.state.filters);
}
});
// Control scroll restoration
history.scrollRestoration = 'manual'; // Manually handle scroll position
// Save scroll position before navigation
window.addEventListener('beforeunload', () => {
const scrollPos = { x: window.scrollX, y: window.scrollY };
history.replaceState({ scrollPos }, '');
});
// Restore scroll position
window.addEventListener('popstate', (e) => {
if (e.state?.scrollPos) {
window.scrollTo(e.state.scrollPos.x, e.state.scrollPos.y);
}
});
Important: pushState() and replaceState() don't trigger popstate event. Only user navigation
(back/forward buttons) or history.back/forward/go() trigger popstate. The title parameter is currently ignored
by most browsers. URLs must be same-origin (protocol + domain + port).
5. Web Workers and Shared Workers
| Worker Type | Scope | Communication | Use Case |
|---|---|---|---|
| Web Worker (Dedicated) | Single page/script | postMessage (1-to-1) | Heavy computation, data processing |
| Shared Worker | Multiple pages/tabs (same origin) | postMessage via ports (many-to-1) | Shared state, cross-tab communication |
| Service Worker | Application-level (all pages) | Events, postMessage | Offline support, caching, push notifications |
| Worker API | Syntax | Description |
|---|---|---|
| new Worker() | const worker = new Worker('worker.js') |
Create dedicated worker from script file |
| postMessage() | worker.postMessage(data) |
Send data to worker (structured clone) |
| onmessage | worker.onmessage = (e) => {} |
Receive messages from worker |
| onerror | worker.onerror = (e) => {} |
Handle worker errors |
| terminate() | worker.terminate() |
Immediately stop worker (from main thread) |
| close() | self.close() |
Stop worker (from inside worker) |
| Worker Context | Available | Not Available |
|---|---|---|
| Global Scope | self, importScripts(), navigator, location |
window, document, parent |
| APIs | fetch, setTimeout, setInterval, IndexedDB, Web Crypto | DOM manipulation, localStorage, sessionStorage |
| Data Transfer | Structured clone, Transferable objects (ArrayBuffer) | Functions, DOM nodes, native objects with methods |
Example: Web Worker for heavy computation
<!-- Main page (main.html) -->
<script>
// Create worker
const worker = new Worker('worker.js');
// Send data to worker
worker.postMessage({
operation: 'fibonacci',
number: 40
});
// Receive results from worker
worker.onmessage = (e) => {
console.log('Result from worker:', e.data);
// { result: 102334155, time: 1523 }
};
// Handle worker errors
worker.onerror = (e) => {
console.error('Worker error:', e.message);
console.error('File:', e.filename, 'Line:', e.lineno);
};
// Send multiple messages
worker.postMessage({ operation: 'sort', data: [5, 2, 8, 1, 9] });
// Terminate worker when done
setTimeout(() => {
worker.terminate();
console.log('Worker terminated');
}, 5000);
</script>
<!-- Worker file (worker.js) -->
<script>
// Listen for messages from main thread
self.onmessage = (e) => {
const { operation, number, data } = e.data;
const startTime = performance.now();
let result;
switch(operation) {
case 'fibonacci':
result = fibonacci(number);
break;
case 'sort':
result = data.sort((a, b) => a - b);
break;
default:
throw new Error('Unknown operation');
}
const time = performance.now() - startTime;
// Send result back to main thread
self.postMessage({ result, time });
};
function fibonacci(n) {
if (n <= 1) return n;
return fibonacci(n - 1) + fibonacci(n - 2);
}
// Import other scripts
importScripts('math-utils.js', 'helpers.js');
// Handle errors
self.onerror = (e) => {
console.error('Error in worker:', e);
};
// Worker can close itself
// self.close();
</script>
Example: Shared Worker for cross-tab communication
<!-- Main page (any tab) -->
<script>
// Create/connect to shared worker
const sharedWorker = new SharedWorker('shared.js');
// Get message port
const port = sharedWorker.port;
// Start the port
port.start();
// Send message
port.postMessage({ type: 'subscribe', channel: 'chat' });
// Receive messages
port.onmessage = (e) => {
console.log('Message from shared worker:', e.data);
};
// Send broadcast to all connected tabs
port.postMessage({
type: 'broadcast',
message: 'Hello from tab ' + Date.now()
});
</script>
<!-- Shared Worker (shared.js) -->
<script>
const connections = [];
// Handle new connections
self.onconnect = (e) => {
const port = e.ports[0];
connections.push(port);
console.log('New connection. Total:', connections.length);
// Listen for messages from this port
port.onmessage = (e) => {
const { type, message, channel } = e.data;
if (type === 'broadcast') {
// Send to all connected tabs
connections.forEach(p => {
if (p !== port) { // Don't send back to sender
p.postMessage(message);
}
});
} else if (type === 'subscribe') {
port.postMessage({ status: 'subscribed', channel });
}
};
// Start the port
port.start();
// Send welcome message
port.postMessage({ type: 'welcome', connections: connections.length });
};
</script>
Performance Tips: Workers run on separate threads - perfect for CPU-intensive tasks (parsing,
image processing, data analysis). Use Transferable objects (ArrayBuffer, MessagePort, ImageBitmap) to transfer
large data without copying. Workers have startup cost - reuse workers instead of creating many short-lived ones.
6. Service Workers and Offline Functionality (PWA)
| Feature | Description | Requirement |
|---|---|---|
| Offline Support | Cache assets and serve when offline | HTTPS required (except localhost) |
| Background Sync | Defer actions until network available | Service Worker API |
| Push Notifications | Receive server push messages | User permission + HTTPS |
| Cache Strategies | Network first, Cache first, Stale-while-revalidate | Cache API |
| Service Worker Lifecycle | Event | When Fired | Actions |
|---|---|---|---|
| 1. Registration | register() |
navigator.serviceWorker.register() called | Download and parse worker script |
| 2. Installation | install |
Worker first installed or updated | Cache static assets |
| 3. Activation | activate |
Worker becomes active (old versions removed) | Clean up old caches |
| 4. Operation | fetch, message |
Network requests, messages from pages | Intercept requests, serve from cache |
| 5. Termination | - |
Browser terminates idle worker | Auto-restart on next event |
| Cache Strategy | Approach | Best For |
|---|---|---|
| Cache First | Try cache, fallback to network | Static assets (CSS, JS, images) |
| Network First | Try network, fallback to cache | API calls, dynamic content |
| Stale While Revalidate | Serve cache, update in background | Frequently updated content |
| Network Only | Always fetch from network | Real-time data, analytics |
| Cache Only | Only serve from cache | Offline-first apps |
Example: Service Worker registration and basic caching
<!-- Main page (index.html) -->
<script>
// Check if service workers are supported
if ('serviceWorker' in navigator) {
// Register service worker
navigator.serviceWorker.register('/service-worker.js')
.then(registration => {
console.log('SW registered:', registration);
console.log('Scope:', registration.scope);
// Check for updates
registration.update();
// Listen for updates
registration.addEventListener('updatefound', () => {
const newWorker = registration.installing;
console.log('New service worker installing');
newWorker.addEventListener('statechange', () => {
if (newWorker.state === 'installed') {
if (navigator.serviceWorker.controller) {
// New worker available, prompt user to reload
console.log('New version available!');
showUpdateNotification();
} else {
console.log('Content cached for offline use');
}
}
});
});
})
.catch(error => {
console.error('SW registration failed:', error);
});
// Listen for messages from service worker
navigator.serviceWorker.addEventListener('message', (e) => {
console.log('Message from SW:', e.data);
});
// Send message to service worker
navigator.serviceWorker.ready.then(registration => {
registration.active.postMessage({ type: 'SKIP_WAITING' });
});
}
</script>
Example: Service Worker with caching strategies (service-worker.js)
const CACHE_NAME = 'my-app-v1';
const STATIC_CACHE = 'static-v1';
const DYNAMIC_CACHE = 'dynamic-v1';
// Files to cache immediately
const STATIC_ASSETS = [
'/',
'/index.html',
'/styles.css',
'/app.js',
'/logo.png',
'/offline.html'
];
// Install event - cache static assets
self.addEventListener('install', (e) => {
console.log('Service Worker installing...');
e.waitUntil(
caches.open(STATIC_CACHE)
.then(cache => {
console.log('Caching static assets');
return cache.addAll(STATIC_ASSETS);
})
.then(() => {
// Skip waiting to activate immediately
return self.skipWaiting();
})
);
});
// Activate event - clean up old caches
self.addEventListener('activate', (e) => {
console.log('Service Worker activating...');
e.waitUntil(
caches.keys()
.then(cacheNames => {
return Promise.all(
cacheNames
.filter(name => name !== STATIC_CACHE && name !== DYNAMIC_CACHE)
.map(name => {
console.log('Deleting old cache:', name);
return caches.delete(name);
})
);
})
.then(() => {
// Take control of all pages immediately
return self.clients.claim();
})
);
});
// Fetch event - implement caching strategies
self.addEventListener('fetch', (e) => {
const { request } = e;
const url = new URL(request.url);
// Strategy 1: Cache First (for static assets)
if (request.destination === 'image' || request.destination === 'style' || request.destination === 'script') {
e.respondWith(
caches.match(request)
.then(cached => {
if (cached) {
return cached; // Return from cache
}
// Not in cache, fetch and cache
return fetch(request).then(response => {
return caches.open(DYNAMIC_CACHE).then(cache => {
cache.put(request, response.clone());
return response;
});
});
})
.catch(() => {
// Return fallback image if offline
return caches.match('/placeholder.png');
})
);
}
// Strategy 2: Network First (for API calls)
else if (url.pathname.startsWith('/api/')) {
e.respondWith(
fetch(request)
.then(response => {
// Cache successful response
const responseClone = response.clone();
caches.open(DYNAMIC_CACHE).then(cache => {
cache.put(request, responseClone);
});
return response;
})
.catch(() => {
// Network failed, try cache
return caches.match(request);
})
);
}
// Strategy 3: Stale While Revalidate (for HTML pages)
else if (request.destination === 'document') {
e.respondWith(
caches.match(request)
.then(cached => {
const fetchPromise = fetch(request).then(response => {
const responseClone = response.clone();
caches.open(DYNAMIC_CACHE).then(cache => {
cache.put(request, responseClone);
});
return response;
});
// Return cached version immediately, update in background
return cached || fetchPromise;
})
.catch(() => {
// Show offline page
return caches.match('/offline.html');
})
);
}
// Default: Network only
else {
e.respondWith(fetch(request));
}
});
// Message event - handle messages from pages
self.addEventListener('message', (e) => {
if (e.data.type === 'SKIP_WAITING') {
self.skipWaiting();
}
// Send message back to client
e.ports[0].postMessage({ status: 'received' });
});
// Background Sync (requires registration.sync.register())
self.addEventListener('sync', (e) => {
if (e.tag === 'sync-data') {
e.waitUntil(syncDataToServer());
}
});
// Push notification
self.addEventListener('push', (e) => {
const data = e.data.json();
e.waitUntil(
self.registration.showNotification(data.title, {
body: data.body,
icon: '/icon.png',
badge: '/badge.png',
data: { url: data.url }
})
);
});
// Notification click
self.addEventListener('notificationclick', (e) => {
e.notification.close();
e.waitUntil(
clients.openWindow(e.notification.data.url)
);
});
Important Limitations: Service Workers only work over HTTPS (except localhost). Cannot access
DOM directly. Runs on separate thread. Cache storage has quota limits (varies by browser). Must handle cache
versioning carefully to avoid serving stale content.
Section 11 Key Takeaways
- localStorage persists across sessions; sessionStorage clears when tab closes - both ~5-10MB limit
- Always JSON.stringify/parse for storing objects in Web Storage; use try-catch for quota errors
- Geolocation requires HTTPS and user permission; enableHighAccuracy uses GPS (drains battery)
- FileReader.readAsDataURL() for image previews; URL.createObjectURL() more efficient for large files
- Always validate file type and size on both client and server; never trust file extensions
- history.pushState() creates new entry; replaceState() modifies current; only popstate on back/forward
- Web Workers run on separate thread - perfect for CPU-intensive tasks; cannot access DOM
- Use Transferable objects (ArrayBuffer) to transfer large data without copying between worker threads
- Service Workers enable offline support, caching, and push notifications; HTTPS required
- Cache strategies: Cache First (static assets), Network First (API), Stale-While-Revalidate (dynamic)