HTM 5 的离线储存的使用和原理

发布于:2025-08-01 ⋅ 阅读:(13) ⋅ 点赞:(0)

1. 离线存储的魅力:为什么它如此重要?

想象一下,你正在用一个在线笔记应用写下灵感,突然 Wi-Fi 断了,页面一片空白,灵感全没了!离线存储就是为了解决这种“断网焦虑”而生。它让网页能在没有网络的情况下继续工作,保存用户数据,甚至展示关键内容。它的核心价值在于:

  • 用户体验的无缝衔接:无论网络状况如何,页面都能保持基本功能。

  • 性能优化:本地存储减少了对服务器的请求,加载速度更快。

  • 数据可靠性:关键数据本地保存,避免因网络抖动导致丢失。

HTML5 提供了多种离线存储工具,每种都有自己的“性格”和适用场景。

2. Web Storage:简单粗暴的键值存储

Web Storage 是 HTML5 离线存储的入门选手,简单易用,适合存储小型数据,比如用户偏好、表单输入或临时状态。它包含两种机制:localStoragesessionStorage。别看它们名字相似,用法和寿命可大不一样!

2.1 localStorage:数据永不“退休”

localStorage 就像一个永不删除的笔记本,数据存储后除非主动清除,否则会一直存在。它的特点是:

  • 存储容量:通常有 5-10MB 的空间(视浏览器而定)。

  • 持久性:数据在浏览器关闭后依然存在。

  • 同源限制:只能被同一域名下的页面访问。

使用场景:保存用户主题设置、登录状态,或者简单的配置信息。

来看一个例子:假设我们要保存用户的昵称和主题偏好。

// 保存用户设置
localStorage.setItem('username', '小明');
localStorage.setItem('theme', 'dark');

// 读取设置
const username = localStorage.getItem('username'); // 小明
const theme = localStorage.getItem('theme'); // dark

// 删除某项
localStorage.removeItem('username');

// 清空所有
localStorage.clear();

注意:localStorage 是同步操作,存储大数据可能会阻塞主线程,所以别往里面塞太多东西!

2.2 sessionStorage:页面关闭就“失忆”

sessionStorage 像个临时便签,页面关闭或标签页关闭后,数据就自动清空。它的特点是:

  • 生命周期:仅在当前会话(标签页)有效。

  • 隔离性:不同标签页的 sessionStorage 互不干扰。

  • 容量:和 localStorage 类似,通常 5-10MB。

使用场景:适合存储临时表单数据,比如用户在填写多页表单时,防止页面刷新丢失输入。

代码示例:保存表单输入的临时数据。

// 假设用户在填写一个多页表单
sessionStorage.setItem('formStep1', JSON.stringify({
  name: '小明',
  email: 'xiaoming@example.com'
}));

// 在另一个页面读取
const formData = JSON.parse(sessionStorage.getItem('formStep1'));
console.log(formData.name); // 小明

2.3 Web Storage 的局限性

虽然 localStorage 和 sessionStorage 简单好用,但它们也有短板:

  • 只能存字符串:复杂对象需要用 JSON.stringify() 和 JSON.parse() 转换。

  • 无结构化查询:不像数据库,无法执行复杂查询。

  • 同步操作:大数据操作可能导致性能问题。

3. IndexedDB:浏览器里的“迷你数据库”

如果说 Web Storage 是便签,那 IndexedDB 就是浏览器里的数据库,功能强大到可以媲美小型服务器端数据库。它支持复杂的结构化数据存储、索引和查询,适合需要处理大量数据的场景,比如离线邮件客户端或复杂的 Web 应用。

3.1 IndexedDB 的核心概念

IndexedDB 的核心是对象存储(Object Store),类似于数据库中的表。它的关键特点包括:

  • 异步操作:通过事件驱动的 API 避免阻塞主线程。

  • 键值存储:支持复杂对象,可以用任意属性作为键。

  • 事务机制:确保数据操作的原子性,类似数据库事务。

  • 容量:通常远超 localStorage,可达几百 MB,甚至更多(视浏览器限制)。

3.2 实战:用 IndexedDB 存储用户笔记

假设我们要构建一个离线笔记应用,允许用户在断网时保存和读取笔记。以下是一个完整的示例:

// 打开或创建数据库
let db;
const request = indexedDB.open('NoteDB', 1);

request.onupgradeneeded = function(event) {
  db = event.target.result;
  // 创建对象存储,设置主键
  const store = db.createObjectStore('notes', { keyPath: 'id', autoIncrement: true });
  // 创建索引,便于按标题查询
  store.createIndex('title', 'title', { unique: false });
};

request.onsuccess = function(event) {
  db = event.target.result;
  console.log('数据库打开成功!');
};

request.onerror = function(event) {
  console.error('数据库打开失败:', event.target.error);
};

// 添加笔记
function addNote(title, content) {
  const transaction = db.transaction(['notes'], 'readwrite');
  const store = transaction.objectStore('notes');
  const note = { title, content, createdAt: new Date() };
  
  const request = store.add(note);
  request.onsuccess = () => console.log('笔记保存成功!');
  request.onerror = () => console.error('保存失败!');
}

// 查询所有笔记
function getAllNotes() {
  const transaction = db.transaction(['notes'], 'readonly');
  const store = transaction.objectStore('notes');
  const request = store.getAll();
  
  request.onsuccess = () => {
    console.log('所有笔记:', request.result);
  };
}

运行代码:调用 addNote('会议记录', '讨论了新项目计划') 添加笔记,调用 getAllNotes() 查看所有笔记。

3.3 IndexedDB 的挑战

虽然强大,IndexedDB 也有学习曲线:

  • API 复杂:异步操作和事务机制需要一定时间适应。

  • 浏览器兼容性:现代浏览器支持良好,但老版本可能有差异。

  • 调试麻烦:需要借助开发者工具查看存储的数据。

小技巧:可以用像 Dexie.js 这样的库来简化 IndexedDB 操作,它封装了复杂的 API,让代码更直观。

4. Cache API:掌控离线资源

Cache API 是 Service Worker 的好搭档,专为离线资源管理而生。它能缓存网页的静态资源(如 HTML、CSS、JS、图片),让页面在断网时也能正常加载。它的核心优势是灵活性和可编程性,可以精细控制哪些资源需要缓存。

4.1 Cache API 的工作原理

Cache API 工作在 Service Worker 的上下文中,流程如下:

  1. 注册 Service Worker:一个独立的 JS 文件,运行在后台,负责拦截网络请求。

  2. 缓存资源:在 Service Worker 的 install 事件中将资源存入 Cache Storage。

  3. 拦截请求:在 fetch 事件中决定是返回缓存资源还是请求网络。

4.2 实战:用 Cache API 实现离线页面

假设我们要缓存一个博客页面的 HTML、CSS 和图片,确保断网时也能访问。以下是完整代码:

index.html

<!DOCTYPE html>
<html lang="zh-CN">
<head>
  <meta charset="UTF-8">
  <title>离线博客</title>
  <link rel="stylesheet" href="style.css">
</head>
<body>
  <h1>我的离线博客</h1>
  <img src="banner.jpg" alt="博客封面">
  <script src="app.js"></script>
  <script>
    // 注册 Service Worker
    if ('serviceWorker' in navigator) {
      navigator.serviceWorker.register('sw.js')
        .then(() => console.log('Service Worker 注册成功'))
        .catch(err => console.error('注册失败:', err));
    }
  </script>
</body>
</html>

sw.js(Service Worker 文件):

const CACHE_NAME = 'blog-cache-v1';
const urlsToCache = [
  '/',
  '/index.html',
  '/style.css',
  '/banner.jpg',
  '/app.js'
];

// 安装 Service Worker,缓存资源
self.addEventListener('install', event => {
  event.waitUntil(
    caches.open(CACHE_NAME)
      .then(cache => {
        console.log('缓存打开成功');
        return cache.addAll(urlsToCache);
      })
  );
});

// 拦截网络请求
self.addEventListener('fetch', event => {
  event.respondWith(
    caches.match(event.request)
      .then(response => {
        // 如果缓存中有,直接返回
        if (response) {
          return response;
        }
        // 否则请求网络
        return fetch(event.request);
      })
  );
});

运行效果:首次加载页面时,资源被缓存到 Cache Storage。断网后,刷新页面依然能看到内容!

4.3 Cache API 的进阶玩法

  • 动态缓存:通过 caches.open() 和 cache.put() 动态添加资源。

  • 缓存更新:通过更改 CACHE_NAME(如 blog-cache-v2)触发缓存更新,清理旧缓存。

  • 离线提示:在 fetch 事件中检测网络状态,提示用户当前是离线模式。

注意:Cache API 依赖 Service Worker,必须在 HTTPS 或 localhost 环境下运行。

5. Application Cache 的兴衰:从明星到弃子

HTML5 的 Application Cache(简称 AppCache)曾经是离线存储的“当红炸子鸡”,它让开发者能指定哪些资源需要缓存,从而实现断网访问。但如今,它已被 W3C 正式废弃,取代它的正是我们上一节提到的 Cache API。尽管如此,了解 AppCache 的历史和教训,能帮助我们更好地理解离线存储的演进,以及如何避免类似的“技术陷阱”。

5.1 AppCache 的工作原理

AppCache 的核心是一个 manifest 文件(通常以 .appcache 结尾),它告诉浏览器要缓存哪些资源、哪些需要在线获取,以及断网时的备用页面。它的基本流程如下:

  1. 创建 manifest 文件:列出需要缓存的资源。

  2. HTML 引用:在 <html> 标签中添加 manifest 属性。

  3. 浏览器处理:浏览器根据 manifest 文件缓存资源,断网时自动使用缓存。

示例 manifest 文件(app.manifest):

CACHE MANIFEST
# 版本 1.0

CACHE:
index.html
style.css
app.js
images/logo.png

NETWORK:
api/data.json

FALLBACK:
/ offline.html

HTML 使用

<html manifest="app.manifest">
<head>
  <title>离线应用</title>
</head>
<body>
  <h1>欢迎体验离线模式</h1>
</body>
</html>

解释

  • CACHE:列出需要缓存的资源。

  • NETWORK:指定必须在线访问的资源。

  • FALLBACK:断网时用 offline.html 替代所有页面。

5.2 为什么 AppCache 被淘汰?

AppCache 看似简单,但问题多多,导致它被开发者吐槽无数,最终被更灵活的 Cache API 取代。以下是它的“罪状”:

  • 更新机制僵硬:只要 manifest 文件内容不变,浏览器就不会更新缓存,即使资源已更新。

  • 调试困难:缓存行为不透明,开发者很难排查问题。

  • 缺乏灵活性:无法动态控制缓存逻辑,远不如 Service Worker 的编程式控制。

  • 兼容性问题:不同浏览器的实现差异大,导致行为不一致。

教训:技术选型时,优先选择灵活、可控的方案,避免过于“黑盒”的工具。Cache API 的出现,正是为了解决这些痛点。

5.3 迁移到 Cache API 的建议

如果你还在维护老项目,可能还得面对 AppCache。迁移到 Cache API 的步骤如下:

  1. 移除 manifest 属性:从 HTML 中删除 manifest="app.manifest"。

  2. 改用 Service Worker:参考上一节的 Cache API 示例,注册 Service Worker 并缓存资源。

  3. 清理旧缓存:在 Service Worker 的 activate 事件中删除 AppCache。

代码示例(清理旧缓存):

self.addEventListener('activate', event => {
  event.waitUntil(
    caches.keys().then(cacheNames => {
      return Promise.all(
        cacheNames.map(cache => {
          if (cache !== 'blog-cache-v1') {
            return caches.delete(cache);
          }
        })
      );
    })
  );
});

6. 离线存储的性能优化:让速度飞起来

离线存储虽然强大,但用不好可能会拖慢页面性能。无论是 Web Storage 的同步阻塞,还是 IndexedDB 的事务开销,都需要精心优化。这节我们来聊聊如何让离线存储既高效又省资源,配上实用技巧和代码示例。

6.1 压缩数据:少存点,省空间

存储空间有限,尤其是 localStorage 和 sessionStorage 的 5-10MB 限制。压缩数据能显著减少占用空间。常用的方法是:

  • JSON 压缩:在存储前用 JSON.stringify() 序列化对象,然后压缩字符串。

  • 第三方库:用像 lz-string 这样的库进行高效压缩。

示例:用 lz-string 压缩 localStorage 数据。

// 引入 lz-string(通过 CDN 或本地)
<script src="https://cdn.jsdelivr.net/npm/lz-string@1.4.4/libs/lz-string.min.js"></script>

// 存储压缩数据
const largeData = { notes: Array(1000).fill({ title: '测试', content: '这是一条很长的笔记...' }) };
const compressed = LZString.compress(JSON.stringify(largeData));
localStorage.setItem('largeNotes', compressed);

// 读取并解压
const decompressed = LZString.decompress(localStorage.getItem('largeNotes'));
const data = JSON.parse(decompressed);
console.log(data.notes.length); // 1000

效果:压缩后数据体积可减少 50%-80%,大大节省存储空间。

6.2 懒加载数据:按需取用

对于 IndexedDB 这样的大型存储,加载全部数据可能会导致性能瓶颈。懒加载是个好办法,只在需要时读取部分数据。

示例:分页加载 IndexedDB 中的笔记。

function getNotesByPage(page = 1, pageSize = 10) {
  const transaction = db.transaction(['notes'], 'readonly');
  const store = transaction.objectStore('notes');
  const request = store.openCursor();
  const results = [];
  let count = 0;

  request.onsuccess = event => {
    const cursor = event.target.result;
    if (!cursor) return;

    // 跳过前面的记录
    if (count >= (page - 1) * pageSize && count < page * pageSize) {
      results.push(cursor.value);
    }
    count++;
    if (count < page * pageSize) {
      cursor.continue();
    } else {
      console.log('分页数据:', results);
    }
  };
}

// 调用:获取第 2 页,每页 10 条
getNotesByPage(2, 10);

好处:只加载当前页数据,减少内存占用,提升响应速度。

6.3 批量操作:减少事务开销

IndexedDB 的事务操作开销较大,频繁开启事务会拖慢性能。批量操作可以合并多次写入或查询。

示例:批量添加多条笔记。

function batchAddNotes(notes) {
  const transaction = db.transaction(['notes'], 'readwrite');
  const store = transaction.objectStore('notes');

  notes.forEach(note => {
    store.add(note);
  });

  transaction.oncomplete = () => console.log('批量添加完成!');
  transaction.onerror = () => console.error('批量添加失败!');
}

// 调用
batchAddNotes([
  { title: '笔记1', content: '内容1' },
  { title: '笔记2', content: '内容2' }
]);

提示:尽量在单个事务中完成所有操作,避免多次开启事务。

7. 安全与隐私考量:保护用户数据

离线存储虽然方便,但也带来了安全和隐私风险。用户数据存储在本地,可能被恶意脚本窃取,或者因浏览器漏洞泄露。这节我们来聊聊如何保护离线存储中的数据,防范潜在威胁。

7.1 防止 XSS 攻击

跨站脚本攻击(XSS)是离线存储的最大威胁之一。恶意脚本可能读取 localStorage 或 IndexedDB 中的敏感数据。防御措施包括:

  • 输入验证:对用户输入进行严格校验,避免注入脚本。

  • 内容安全策略(CSP):通过 <meta> 标签或服务器配置限制脚本来源。

示例:添加 CSP 防止 XSS。

<meta http-equiv="Content-Security-Policy" content="default-src 'self'; script-src 'self'">

这行代码限制页面只能加载同源脚本,防止外部恶意脚本访问存储。

7.2 加密敏感数据

对于敏感信息(如用户 token 或个人资料),存储前应加密。可以用 crypto.subtle API 进行加密。

示例:加密存储用户 token。

async function encryptAndStore(token) {
  const encoder = new TextEncoder();
  const data = encoder.encode(token);
  const key = await crypto.subtle.generateKey(
    { name: 'AES-GCM', length: 256 },
    true,
    ['encrypt', 'decrypt']
  );
  const iv = crypto.getRandomValues(new Uint8Array(12));
  const encrypted = await crypto.subtle.encrypt(
    { name: 'AES-GCM', iv },
    key,
    data
  );

  // 存储加密数据和 iv
  localStorage.setItem('encryptedToken', btoa(String.fromCharCode(...new Uint8Array(encrypted))));
  localStorage.setItem('iv', btoa(String.fromCharCode(...iv)));
}

注意:加密密钥需妥善管理,避免存储在客户端。

7.3 清理无用数据

长期存储的数据可能成为隐私隐患。定期清理过时数据是个好习惯。

示例:清理过期笔记。

function clearOldNotes(days = 30) {
  const transaction = db.transaction(['notes'], 'readwrite');
  const store = transaction.objectStore('notes');
  const request = store.openCursor();

  request.onsuccess = event => {
    const cursor = event.target.result;
    if (!cursor) return;

    const note = cursor.value;
    const age = (new Date() - new Date(note.createdAt)) / (1000 * 60 * 60 * 24);
    if (age > days) {
      cursor.delete();
    }
    cursor.continue();
  };
}

8. 综合案例:打造一个离线 To-Do 应用

现在,我们要把前面学到的离线存储技术整合起来,打造一个真正的离线 To-Do 应用!这个应用将允许用户在断网时添加、编辑、删除任务,并确保数据和界面都能正常工作。我们会结合 Web Storage 保存用户偏好,IndexedDB 存储任务数据,Cache API 缓存静态资源,打造一个健壮的离线体验。准备好了吗?让我们从头开始,代码、逻辑、细节一个不少!

8.1 应用功能与技术栈

功能需求

  • 用户可以添加、编辑、删除任务。

  • 支持离线操作,断网时数据不丢失,界面可访问。

  • 保存用户偏好(如主题颜色)。

  • 提供简单的搜索功能,按关键字查找任务。

技术选型

  • localStorage:存储用户主题偏好。

  • IndexedDB:存储任务列表,支持复杂查询。

  • Cache API + Service Worker:缓存 HTML、CSS、JS 和图片,确保离线访问。

  • HTML5 + CSS + JS:构建简单直观的界面。

8.2 项目结构

项目文件结构如下:

todo-app/
├── index.html       # 主页面
├── style.css        # 样式文件
├── app.js           # 核心逻辑
├── sw.js            # Service Worker
└── offline.html     # 离线提示页面

8.3 代码实现

8.3.1 主页面:index.html
<!DOCTYPE html>
<html lang="zh-CN">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>离线 To-Do 应用</title>
  <link rel="stylesheet" href="style.css">
</head>
<body>
  <div class="container">
    <h1>我的 To-Do 列表</h1>
    <div class="controls">
      <input type="text" id="taskInput" placeholder="输入新任务...">
      <button onclick="addTask()">添加</button>
      <input type="text" id="searchInput" placeholder="搜索任务...">
      <select id="themeSelect" onchange="changeTheme()">
        <option value="light">浅色主题</option>
        <option value="dark">深色主题</option>
      </select>
    </div>
    <ul id="taskList"></ul>
  </div>
  <script src="app.js"></script>
  <script>
    // 注册 Service Worker
    if ('serviceWorker' in navigator) {
      navigator.serviceWorker.register('sw.js')
        .then(() => console.log('Service Worker 注册成功'))
        .catch(err => console.error('注册失败:', err));
    }
  </script>
</body>
</html>
8.3.2 样式:style.css
body {
  font-family: Arial, sans-serif;
  margin: 0;
  padding: 20px;
  transition: background-color 0.3s;
}
.container {
  max-width: 600px;
  margin: auto;
}
.controls {
  display: flex;
  gap: 10px;
  margin-bottom: 20px;
}
input, button, select {
  padding: 8px;
  font-size: 16px;
}
ul {
  list-style: none;
  padding: 0;
}
li {
  display: flex;
  justify-content: space-between;
  padding: 10px;
  border-bottom: 1px solid #ddd;
}
.light { background-color: #f9f9f9; color: #333; }
.dark { background-color: #333; color: #fff; }
8.3.3 核心逻辑:app.js
// 初始化 IndexedDB
let db;
const request = indexedDB.open('TodoDB', 1);

request.onupgradeneeded = event => {
  db = event.target.result;
  const store = db.createObjectStore('tasks', { keyPath: 'id', autoIncrement: true });
  store.createIndex('title', 'title', { unique: false });
};

request.onsuccess = event => {
  db = event.target.result;
  loadTasks();
  loadTheme();
};

request.onerror = event => console.error('数据库错误:', event.target.error);

// 添加任务
function addTask() {
  const input = document.getElementById('taskInput');
  const title = input.value.trim();
  if (!title) return;

  const transaction = db.transaction(['tasks'], 'readwrite');
  const store = transaction.objectStore('tasks');
  const task = { title, createdAt: new Date() };
  store.add(task).onsuccess = () => {
    input.value = '';
    loadTasks();
  };
}

// 加载任务
function loadTasks(query = '') {
  const transaction = db.transaction(['tasks'], 'readonly');
  const store = transaction.objectStore('tasks');
  const request = store.getAll();

  request.onsuccess = () => {
    const tasks = request.result.filter(task => 
      query ? task.title.includes(query) : true
    );
    const taskList = document.getElementById('taskList');
    taskList.innerHTML = tasks.map(task => `
      <li>
        ${task.title} <button onclick="deleteTask(${task.id})">删除</button>
      </li>
    `).join('');
  };
}

// 删除任务
function deleteTask(id) {
  const transaction = db.transaction(['tasks'], 'readwrite');
  const store = transaction.objectStore('tasks');
  store.delete(id).onsuccess = () => loadTasks();
}

// 搜索任务
document.getElementById('searchInput').addEventListener('input', e => {
  loadTasks(e.target.value);
});

// 主题切换
function changeTheme() {
  const theme = document.getElementById('themeSelect').value;
  document.body.className = theme;
  localStorage.setItem('theme', theme);
}

function loadTheme() {
  const theme = localStorage.getItem('theme') || 'light';
  document.body.className = theme;
  document.getElementById('themeSelect').value = theme;
}
8.3.4 Service Worker:sw.js
const CACHE_NAME = 'todo-cache-v1';
const urlsToCache = [
  '/',
  '/index.html',
  '/style.css',
  '/app.js',
  '/offline.html'
];

self.addEventListener('install', event => {
  event.waitUntil(
    caches.open(CACHE_NAME).then(cache => {
      return cache.addAll(urlsToCache);
    })
  );
});

self.addEventListener('fetch', event => {
  event.respondWith(
    caches.match(event.request).then(response => {
      return response || fetch(event.request).catch(() => {
        return caches.match('/offline.html');
      });
    })
  );
});
8.3.5 离线页面:offline.html
<!DOCTYPE html>
<html lang="zh-CN">
<head>
  <meta charset="UTF-8">
  <title>离线模式</title>
  <link rel="stylesheet" href="style.css">
</head>
<body>
  <div class="container">
    <h1>当前处于离线模式</h1>
    <p>您仍然可以管理任务,但搜索功能可能受限。请连接网络以获取完整功能。</p>
  </div>
</body>
</html>

8.4 运行与测试

  1. 部署:将文件放在支持 HTTPS 的服务器或本地 localhost 运行(Service Worker 要求安全上下文)。

  2. 测试离线:打开浏览器开发者工具,切换到“离线”模式,刷新页面,确认任务列表和主题仍可操作。

  3. 效果:任务数据保存在 IndexedDB,主题保存在 localStorage,静态资源由 Cache API 提供,断网时显示 offline.html。

小技巧

  • 用 Chrome DevTools 的“Application”面板查看 IndexedDB 和 Cache Storage 数据。

  • 添加版本控制(如 todo-cache-v2)以更新缓存。

9. 调试与工具推荐:让问题无处遁形

离线存储的开发过程中,调试是个绕不开的环节。数据没存对?缓存没更新?还是 Service Worker 出了岔子?别慌,这节我们介绍几款实用工具和调试技巧,帮你快速定位问题。

9.1 Chrome DevTools:你的调试利器

Chrome 的开发者工具是调试离线存储的首选,功能强大且直观。以下是关键功能:

  • Application 面板

    • Storage:查看 localStorage 和 sessionStorage 的键值对。

    • IndexedDB:浏览数据库、对象存储和索引,实时查看数据。

    • Cache Storage:检查 Service Worker 缓存的资源。

  • Network 面板:模拟离线环境,验证缓存效果。

  • Service Worker 面板:检查注册状态、强制更新或注销。

调试示例:检查 IndexedDB 数据。

  1. 打开 DevTools(F12)。

  2. 切换到“Application”面板,展开“IndexedDB”部分。

  3. 选择 TodoDB -> tasks,查看存储的任务数据。

  4. 手动删除某条记录,验证 deleteTask 函数效果。

9.2 Firefox 开发者工具:轻量但实用

Firefox 的存储调试工具虽然没有 Chrome 那么细致,但也很好用:

  • Storage 面板:查看 localStorage、sessionStorage 和 IndexedDB 数据。

  • Network 面板:支持离线模式模拟。

  • Console:输出 Service Worker 的日志,便于调试。

小技巧:Firefox 的 IndexedDB 查看器支持直接编辑数据,适合快速测试。

9.3 第三方库与工具

  • Dexie.js:简化 IndexedDB 操作,提供 Promise 风格 API,调试时可减少代码复杂度。

  • Workbox:Google 提供的 Service Worker 库,内置调试工具,方便管理缓存。

  • Postman:模拟网络请求,测试 Service Worker 的拦截逻辑。

示例:用 Dexie.js 重写任务查询。

const db = new Dexie('TodoDB');
db.version(1).stores({ tasks: '++id,title' });

async function loadTasks(query = '') {
  const tasks = await db.tasks
    .filter(task => query ? task.title.includes(query) : true)
    .toArray();
  document.getElementById('taskList').innerHTML = tasks.map(task => `
    <li>${task.title} <button onclick="deleteTask(${task.id})">删除</button></li>
  `).join('');
}

效果:代码更简洁,调试时更容易定位问题。


网站公告

今日签到

点亮在社区的每一天
去签到