前置知识: HTML5

HTML5 离线存储与 Web API

00:00
10 min Intermediate

localStorage、sessionStorage、IndexedDB 与 Web Workers。

1. Web 存储 (Web Storage)

Web Storage 提供了一种在浏览器中存储键值对数据的机制,相比 Cookie 具有更大的容量 (通常为 5MB+) 和更简单的 API。

1.1 localStorage

特点

  • 数据永久存储,除非手动清除
  • 同一域名下的所有页面共享数据
  • 数据不会随 HTTP 请求发送到服务器 操作方法
// 存储数据
localStorage.setItem('name', 'Alice');
localStorage.setItem('age', '30');
// 读取数据
const name = localStorage.getItem('name');
const age = localStorage.getItem('age');
console.log(name, age); // 输出: Alice 30
// 删除数据
localStorage.removeItem('age');
// 清除所有数据
localStorage.clear();
// 遍历所有键值对
for (let i = 0; i < localStorage.length; i++) {
  const key = localStorage.key(i);
  const value = localStorage.getItem(key);
  console.log(`${key}: ${value}`);
}

存储对象: localStorage 只能存储字符串,存储对象需要先序列化:

// 存储对象
const user = { name: 'Bob', age: 25, email: 'bob@example.com' };
localStorage.setItem('user', JSON.stringify(user));
// 读取对象
const storedUser = JSON.parse(localStorage.getItem('user'));
console.log(storedUser.name); // 输出: Bob

1.2 sessionStorage

特点

  • 数据仅在当前会话 (标签页) 有效,关闭标签页即失效
  • 不同标签页之间的数据不共享
  • 刷新页面数据仍然保留 操作方法
// 存储数据
sessionStorage.setItem('token', 'abc123');
// 读取数据
const token = sessionStorage.getItem('token');
// 删除数据
sessionStorage.removeItem('token');
// 清除所有数据
sessionStorage.clear();
特性localStoragesessionStorageCookie
存储容量约 5MB约 5MB约 4KB
存储时间永久会话期间可设置过期时间
服务器发送是 (随请求发送)
作用域同一域名同一标签页可设置路径
API 复杂度简单简单复杂

1.4 使用场景

  • localStorage:存储用户偏好设置、主题选择、登录状态等需要长期保存的数据
  • sessionStorage:存储临时会话数据、表单数据、购物车内容等仅在当前会话有效的数据

2. 地理定位 (Geolocation API)

Geolocation API 允许网页获取用户的地理位置信息,可用于地图应用、位置服务等场景。

2.1 基本用法

// 获取当前位置
navigator.geolocation.getCurrentPosition(
  (position) => {
    console.log('纬度: ' + position.coords.latitude);
    console.log('经度: ' + position.coords.longitude);
    console.log('精度: ' + position.coords.accuracy + ' 米');
  },
  (error) => {
    console.error('获取位置失败:', error.message);
  }
);

2.2 监听位置变化

// 监听位置变化
const watchId = navigator.geolocation.watchPosition(
  (position) => {
    console.log('当前位置:', position.coords.latitude, position.coords.longitude);
  },
  (error) => {
    console.error('获取位置失败:', error.message);
  },
  {
    enableHighAccuracy: true, // 启用高精度模式
    timeout: 5000, // 超时时间
    maximumAge: 0, // 不使用缓存
  }
);
// 停止监听
// navigator.geolocation.clearWatch(watchId);

2.3 位置对象属性

属性描述
coords.latitude纬度
coords.longitude经度
coords.accuracy位置精度 (米)
coords.altitude海拔高度 (米)
coords.altitudeAccuracy海拔高度精度 (米)
coords.heading方向 (度,从正北开始顺时针计算)
coords.speed速度 (米/秒)
timestamp获取位置的时间戳

2.4 错误处理

错误代码描述
0未知错误
1用户拒绝了位置请求
2位置不可用
3请求超时

2.5 使用场景

  • 地图应用:显示用户当前位置
  • 位置服务:附近的餐厅、商店等
  • 导航应用:提供路线规划
  • 社交应用:分享位置信息

3. Web Workers

Web Workers 允许在后台线程运行脚本,不阻塞 UI 渲染,适合处理大量计算任务。

3.1 基本用法

创建 Worker

// main.js
const worker = new Worker('worker.js');
// 发送消息给 Worker
worker.postMessage({ type: 'calculate', data: 1000000 });
// 接收 Worker 消息
worker.onmessage = function (event) {
  console.log('计算结果:', event.data);
};
// 处理错误
worker.onerror = function (error) {
  console.error('Worker 错误:', error);
};

Worker 脚本 (worker.js)

// 接收消息
self.onmessage = function (event) {
  const { type, data } = event.data;
  if (type === 'calculate') {
    // 执行密集计算
    let result = 0;
    for (let i = 0; i < data; i++) {
      result += i;
    }
    // 发送结果
    self.postMessage(result);
  }
};

3.2 终止 Worker

// 终止 Worker
worker.terminate();

3.3 类型

  • Dedicated Workers:专用 Worker,只能被创建它的脚本使用
  • Shared Workers:共享 Worker,可以被多个脚本使用
  • Service Workers:用于离线缓存和后台同步

3.4 使用场景

  • 密集计算:数学运算、图像处理
  • 数据处理:大数据集分析、排序
  • 后台任务:文件上传、数据同步

4. 离线应用 (Service Workers)

Service Workers 是一种特殊的 Web Worker,用于拦截网络请求、实现离线缓存,是 Progressive Web App (PWA) 的核心技术。

4.1 注册 Service Worker

// 注册 Service Worker
if ('serviceWorker' in navigator) {
  window.addEventListener('load', async () => {
    try {
      const registration = await navigator.serviceWorker.register('/sw.js');
      console.log('Service Worker 注册成功:', registration.scope);
    } catch (error) {
      console.error('Service Worker 注册失败:', error);
    }
  });
}

4.2 Service Worker 脚本 (sw.js)

 // 缓存名称
 const CACHE_NAME = 'my-cache-v1';
 // 需要缓存的资源
 const urlsToCache = [
  '/',
  '/index.html',
  '/styles.css',
  '/script.js',
  '/images/logo.png'
 ]
 // 安装事件 - 缓存资源
 self.addEventListener('install', (event) => {
  event.waitUntil(
  caches.open(CACHE_NAME)
  .then((cache) => {
  console.log('打开缓存');
  return cache.addAll(urlsToCache);
  })
  );
 }
 // 激活事件 - 清理旧缓存
 self.addEventListener('activate', (event) => {
  const cacheWhitelist = [CACHE_NAME];
  event.waitUntil(
  caches.keys().then((cacheNames) => {
  return Promise.all(
  cacheNames.map((cacheName) => {
  if (cacheWhitelist.indexOf(cacheName) === -1) {
  return caches.delete(cacheName);
  }
  })
  );
  })
  );
 }
 // fetch 事件 - 拦截网络请求
 self.addEventListener('fetch', (event) => {
  event.respondWith(
  caches.match(event.request)
  .then((response) => {
  // 如果缓存中有响应,则返回缓存
  if (response) {
  return response;
  }
  // 否则发起网络请求
  return fetch(event.request)
  .then((response) => {
  // 检查响应是否有效
  if (!response || response.status !== 200 || response.type !== 'basic') {
  return response;
  }
  // 克隆响应
  const responseToCache = response.clone();
  // 将响应添加到缓存
  caches.open(CACHE_NAME)
  .then((cache) => {
  cache.put(event.request, responseToCache);
  });
  return response;
  });
  })
  );
 }

4.3 缓存策略

  • Cache First:优先从缓存获取,无缓存再请求网络
  • Network First:优先从网络获取,网络失败再从缓存获取
  • Cache Only:只从缓存获取
  • Network Only:只从网络获取
  • Stale While Revalidate:先从缓存获取,同时请求网络更新缓存

4.4 使用场景

  • 离线应用:即使没有网络也能访问应用
  • 性能优化:缓存静态资源,提高加载速度
  • 后台同步:在网络可用时同步数据
  • 推送通知:即使应用未打开也能收到通知

5. Fetch API

Fetch API 是现代化的异步网络请求方案,替代原生的 XMLHttpRequest,提供了更简洁、灵活的 API。

5.1 基本用法

// GET 请求
fetch('https://api.example.com/data')
  .then((response) => {
    if (!response.ok) {
      throw new Error('网络响应失败');
    }
    return response.json();
  })
  .then((data) => {
    console.log('数据:', data);
  })
  .catch((error) => {
    console.error('错误:', error);
  });

5.2 POST 请求

 // POST 请求
 fetch('https://api.example.com/users', {
  method: 'POST',
  headers: {
  'Content-Type': 'application/json'
  },
  body: JSON.stringify({
  name: 'John Doe',
  email: 'john@example.com'
  })
 }
  .then((response) => response.json())
  .then((data) => {
  console.log('创建用户成功:', data);
  })
  .catch((error) => {
  console.error('错误:', error);
  });

5.3 请求选项

const options = {
  method: 'GET', // GET, POST, PUT, DELETE, etc.
  headers: {
    'Content-Type': 'application/json',
    Authorization: 'Bearer token123',
  },
  body: JSON.stringify(data), // POST 请求时使用
  mode: 'cors', // cors, no-cors, same-origin
  credentials: 'include', // include, same-origin, omit
  cache: 'default', // default, no-store, reload, no-cache, force-cache, only-if-cached
  redirect: 'follow', // follow, error, manual
  referrer: 'no-referrer', // no-referrer, client
  referrerPolicy: 'no-referrer',
  integrity: 'sha256-abc123',
  keepalive: false,
  signal: abortController.signal, // 用于取消请求
};
fetch('https://api.example.com/data', options)
  .then((response) => response.json())
  .then((data) => console.log(data));

5.4 取消请求

 // 创建 AbortController
 const abortController = new AbortController();
 // 发送请求
 fetch('https://api.example.com/data', {
  signal: abortController.signal
 }
  .then((response) => response.json())
  .then((data) => console.log(data))
  .catch((error) => {
  if (error.name === 'AbortError') {
  console.log('请求已取消');
  } else {
  console.error('错误:', error);
  }
  });
 // 取消请求
 setTimeout(() => {
  abortController.abort();
 }

5.5 与 async/await 结合

async function fetchData() {
  try {
    const response = await fetch('https://api.example.com/data');
    if (!response.ok) {
      throw new Error('网络响应失败');
    }
    const data = await response.json();
    console.log('数据:', data);
    return data;
  } catch (error) {
    console.error('错误:', error);
    throw error;
  }
}
// 调用函数
fetchData();

6. 其他 Web API

6.1 Notification API

用于向用户显示通知:

// 请求通知权限
if ('Notification' in window) {
  Notification.requestPermission().then((permission) => {
    if (permission === 'granted') {
      // 发送通知
      new Notification('通知标题', {
        body: '通知内容',
        icon: '/images/icon.png',
      });
    }
  });
}

6.2 Intersection Observer API

用于检测元素是否进入视口:

// 创建 Intersection Observer
const observer = new IntersectionObserver((entries) => {
  entries.forEach((entry) => {
    if (entry.isIntersecting) {
      // 元素进入视口
      console.log('元素进入视口');
      entry.target.classList.add('visible');
    } else {
      // 元素离开视口
      console.log('元素离开视口');
      entry.target.classList.remove('visible');
    }
  });
});
// 观察元素
const element = document.querySelector('.target');
observer.observe(element);

6.3 File API

用于处理文件上传和读取:

// 监听文件选择
const fileInput = document.querySelector('input[type="file"]');
fileInput.addEventListener('change', (event) => {
  const file = event.target.files[0];
  // 检查文件类型
  if (file.type.startsWith('image/')) {
    // 读取文件
    const reader = new FileReader();
    reader.onload = (e) => {
      // 显示图片
      const img = document.createElement('img');
      img.src = e.target.result;
      document.body.appendChild(img);
    };
    reader.readAsDataURL(file);
  }
});

6.4 Canvas API

用于绘制图形:

const canvas = document.getElementById('myCanvas');
const ctx = canvas.getContext('2d');
// 绘制矩形
ctx.fillStyle = 'red';
ctx.fillRect(10, 10, 100, 50);
// 绘制圆形
ctx.beginPath();
ctx.arc(150, 100, 30, 0, Math.PI * 2);
ctx.fillStyle = 'blue';
ctx.fill();

7. 实际应用示例

7.1 示例 1:本地存储用户偏好

<!DOCTYPE html>
<html lang="zh-CN">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>本地存储用户偏好</title>
    <style>
      body {
        font-family: Arial, sans-serif;
        line-height: 1.6;
        margin: 0;
        padding: 2rem;
        transition:
          background-color 0.3s,
          color 0.3s;
      }
      .container {
        max-width: 600px;
        margin: 0 auto;
        background-color: white;
        padding: 2rem;
        border-radius: 5px;
        box-shadow: 0 2px 5px rgba(0, 0, 0, 0.1);
      }
      h1 {
        text-align: center;
        margin-bottom: 2rem;
      }
      .theme-toggle {
        display: flex;
        align-items: center;
        margin-bottom: 2rem;
      }
      .theme-toggle label {
        margin-right: 1rem;
      }
      .dark-theme {
        background-color: #333;
        color: white;
      }
      .dark-theme .container {
        background-color: #444;
      }
    </style>
  </head>
  <body>
    <div class="container">
      <h1>本地存储用户偏好</h1>
      <div class="theme-toggle">
        <label for="darkMode">深色模式:</label>
        <input type="checkbox" id="darkMode" />
      </div>
      <p>此示例展示如何使用 localStorage 存储用户的主题偏好。</p>
      <p>当你切换主题时,偏好会被保存到本地存储,下次打开页面时会自动应用。</p>
    </div>
    <script>
      const darkModeToggle = document.getElementById('darkMode');
      const body = document.body;
      // 加载保存的主题偏好
      const savedTheme = localStorage.getItem('darkMode');
      if (savedTheme === '') {
        body.classList.add('dark-theme');
        darkModeToggle.checked = true;
      }
      // 监听主题切换
      darkModeToggle.addEventListener('change', function () {
        if (this.checked) {
          body.classList.add('dark-theme');
          localStorage.setItem('darkMode', '');
        } else {
          body.classList.remove('dark-theme');
          localStorage.setItem('darkMode', 'false');
        }
      });
    </script>
  </body>
</html>

7.2 示例 2:地理定位应用

<!DOCTYPE html>
<html lang="zh-CN">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>地理定位应用</title>
    <style>
      body {
        font-family: Arial, sans-serif;
        line-height: 1.6;
        margin: 0;
        padding: 2rem;
        background-color: #f4f4f4;
      }
      .container {
        max-width: 600px;
        margin: 0 auto;
        background-color: white;
        padding: 2rem;
        border-radius: 5px;
        box-shadow: 0 2px 5px rgba(0, 0, 0, 0.1);
      }
      h1 {
        text-align: center;
        margin-bottom: 2rem;
      }
      .location-info {
        margin-top: 2rem;
        padding: 1rem;
        background-color: #f9f9f9;
        border-radius: 5px;
      }
      button {
        padding: 0.5rem 1rem;
        background-color: #4caf50;
        color: white;
        border: none;
        border-radius: 4px;
        cursor: pointer;
      }
      button:hover {
        background-color: #45a049;
      }
      .error {
        color: red;
        margin-top: 1rem;
      }
    </style>
  </head>
  <body>
    <div class="container">
      <h1>地理定位应用</h1>
      <button id="getLocation">获取当前位置</button>
      <div class="location-info" id="locationInfo"></div>
      <div class="error" id="errorMessage"></div>
    </div>
    <script>
      const getLocationBtn = document.getElementById('getLocation');
      const locationInfo = document.getElementById('locationInfo');
      const errorMessage = document.getElementById('errorMessage');
      getLocationBtn.addEventListener('click', function () {
        if ('geolocation' in navigator) {
          locationInfo.innerHTML = '<p>正在获取位置...</p>';
          errorMessage.textContent = '';
          navigator.geolocation.getCurrentPosition(
            (position) => {
              const { latitude, longitude, accuracy } = position.coords;
              locationInfo.innerHTML = `
  <h3>当前位置</h3>
  <p>纬度: ${latitude.toFixed(6)}</p>
  <p>经度: ${longitude.toFixed(6)}</p>
  <p>精度: ${accuracy.toFixed(2)} 米</p>
  <p>时间: ${new Date(position.timestamp).toLocaleString()}</p>
  `;
            },
            (error) => {
              let errorText = '';
              switch (error.code) {
                case error.PERMISSION_DENIED:
                  errorText = '用户拒绝了位置请求';
                  break;
                case error.POSITION_UNAVAILABLE:
                  errorText = '位置信息不可用';
                  break;
                case error.TIMEOUT:
                  errorText = '获取位置超时';
                  break;
                default:
                  errorText = '获取位置时发生未知错误';
              }
              errorMessage.textContent = errorText;
              locationInfo.innerHTML = '';
            },
            {
              enableHighAccuracy: true,
              timeout: 10000,
              maximumAge: 0,
            }
          );
        } else {
          errorMessage.textContent = '您的浏览器不支持地理定位';
        }
      });
    </script>
  </body>
</html>

7.3 示例 3:使用 Fetch API 获取数据

<!DOCTYPE html>
<html lang="zh-CN">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Fetch API 示例</title>
    <style>
      body {
        font-family: Arial, sans-serif;
        line-height: 1.6;
        margin: 0;
        padding: 2rem;
        background-color: #f4f4f4;
      }
      .container {
        max-width: 800px;
        margin: 0 auto;
        background-color: white;
        padding: 2rem;
        border-radius: 5px;
        box-shadow: 0 2px 5px rgba(0, 0, 0, 0.1);
      }
      h1 {
        text-align: center;
        margin-bottom: 2rem;
      }
      button {
        padding: 0.5rem 1rem;
        background-color: #008cba;
        color: white;
        border: none;
        border-radius: 4px;
        cursor: pointer;
        margin-bottom: 1rem;
      }
      button:hover {
        background-color: #007b9e;
      }
      .posts {
        margin-top: 2rem;
      }
      .post {
        padding: 1rem;
        border-bottom: 1px solid #ddd;
      }
      .post:last-child {
        border-bottom: none;
      }
      .post h3 {
        margin-top: 0;
      }
      .loading {
        text-align: center;
        padding: 2rem;
      }
      .error {
        color: red;
        text-align: center;
        padding: 2rem;
      }
    </style>
  </head>
  <body>
    <div class="container">
      <h1>Fetch API 示例</h1>
      <button id="fetchPosts">获取帖子</button>
      <div class="posts" id="postsContainer"></div>
    </div>
    <script>
      const fetchPostsBtn = document.getElementById('fetchPosts');
      const postsContainer = document.getElementById('postsContainer');
      fetchPostsBtn.addEventListener('click', async function () {
        try {
          postsContainer.innerHTML = '<div class="loading">加载中...</div>';
          // 使用 Fetch API 获取数据
          const response = await fetch('https://jsonplaceholder.typicode.com/posts?_limit=10');
          if (!response.ok) {
            throw new Error('网络响应失败');
          }
          const posts = await response.json();
          // 渲染帖子
          postsContainer.innerHTML = posts
            .map(
              (post) => `
  <div class="post">
  <h3>${post.title}</h3>
  <p>${post.body}</p>
  </div>
  `
            )
            .join('');
        } catch (error) {
          postsContainer.innerHTML = `<div class="error">错误: ${error.message}</div>`;
        }
      });
    </script>
  </body>
</html>

8. 最佳实践

8.1 Web Storage 最佳实践

  • 数据类型:localStorage 和 sessionStorage 只能存储字符串,存储对象时需要使用 JSON.stringify() 和 JSON.parse()
  • 存储容量:不要存储过大的数据,避免超出存储限制
  • 敏感数据:不要存储敏感数据(如密码),这些数据应该存储在服务器端
  • 性能:频繁读写 localStorage 可能影响性能,建议批量操作
  • 兼容性:虽然现代浏览器都支持 Web Storage,但仍需考虑旧浏览器的兼容性

8.2 Geolocation API 最佳实践

  • 权限请求:在需要时才请求位置权限,不要在页面加载时就请求
  • 错误处理:妥善处理位置获取失败的情况
  • 精度设置:根据实际需求设置精度,高精度模式会消耗更多电量
  • 用户隐私:尊重用户隐私,明确告知用户位置信息的使用目的

8.3 Web Workers 最佳实践

  • 适用场景:只在需要处理大量计算时使用 Web Workers,避免过度使用
  • 通信开销:注意 Worker 与主线程之间的通信开销,避免频繁通信
  • 资源管理:在不需要时及时终止 Worker,避免资源浪费
  • 错误处理:妥善处理 Worker 中的错误

8.4 Service Workers 最佳实践

  • 缓存策略:根据资源类型选择合适的缓存策略
  • 缓存版本:合理管理缓存版本,避免缓存过期问题
  • 网络请求:正确处理网络请求,避免无限循环
  • 调试:使用 Chrome DevTools 进行 Service Worker 调试
  • 更新:正确处理 Service Worker 的更新流程

8.5 Fetch API 最佳实践

  • 错误处理:始终处理 fetch 请求的错误,包括网络错误和 HTTP 错误
  • 请求配置据实际需求配置请求选项,如 headers、credentials 等
  • 响应处理响应类型选择合适的处理方法,如 response.json()、response.text() 等
  • 取消请求:在需要时使用 AbortController 取消请求
  • 超时处理实现请求超时处理,避免长时间等待

8.6 通用最佳实践

  • 特性检测:在使用 Web API 前进特性检测,确保浏览器支持
  • 性能优化注意 API 的性能影响,避免过使用
  • 安全:遵循安全最佳实践,避免 XSS、CSRF 等攻击
  • 可访问性:确保应用对所有用户可访问,括使用辅助技术的用户
  • 测试:在不同浏览器设备测试应用,确保兼容性

更新日志 (Changelog)

  • 2026-04-05: 整合 Web 存储与现代 Web API
  • 2026-04-05: 扩写内容增加详细的 Web 存储、地理定Web WorkersService Workers、Fetch API概念、示例和最佳实践,以及其他常用 Web API 和实际应用示例。

知识检测

学习进度

-- 已学文档
--% 知识覆盖率

学习推荐

专注模式