HTML5 APIs and Advanced Features

1. Web Storage (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

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

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 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

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)