HTML 基础 · 41/88
1. 微信小程序 bindtap 和 catchtap 的区别 2. Bootstrap 徽章 3. Bootstrap 按钮下拉菜单 4. Bootstrap 按钮组 5. Bootstrap 按钮激活与禁用 6. Bootstrap 文档类型声明 7. Bootstrap 下拉菜单 8. Bootstrap 表单帮助文本 9. Bootstrap 水平表单 10. Bootstrap 输入框组 11. Bootstrap 超大屏幕 12. Bootstrap 标签 13. Bootstrap 导航类型 14. Bootstrap 分页 15. Bootstrap 响应式表格 16. Bootstrap 垂直表单创建 17. 浏览器乱码问题及解决方案 18. Canvas 标签属性与 CSS 样式设置宽高的区别 19. Canvas、SVG、WebGL 对比 20. 网页验证码的作用 21. 前端跨页面通信方法 22. 圆形可点击区域实现 23. 浏览器多标签页通信方案 24. CSSOM 树和 DOM 树的解析时机 25. 设备的 DPR 是否可变 26. 禁用 a 标签的跳转和定位 27. DOM 和 BOM 的区别 28. DOM 发展历程 29. DOCTYPE 与文档模式 30. DNS 预解析优化网页加载速度 31. DOM 树的理解 32. Drag API 拖拽事件 33. 前端 SEO 优化要点 34. 标题与副标题的实现 35. HTML 元素分类 36. HTML 全局属性 37. HTML 页面渲染过程 38. HTML 语义化 39. HTML 语义化 40. HTML5 DOCTYPE 声明简化原因 41. HTML5 离线存储原理与使用 42. HTML5 新特性 43. HTML5 移除的元素 44. IconFont 字体图标 45. iframe 的优缺点与通信 46. 图片点击下载而非预览 47. HTML 和 CSS 中的图片加载与渲染规则 48. 浏览器预览待上传图片 49. img 标签 title 和 alt 的区别 50. input 标签触发拍照功能 51. 控制 input 输入框字数 52. img 的 srcset 属性 53. 禁止 input 显示历史记录 54. input 上传多个文件 55. JS 和 CSS 对 DOM 树构建的影响 56. label 标签的作用 57. link 和 @import 的区别 58. Meta 标签常用属性 59. Meta 标签自动刷新跳转 60. 小程序的双线程架构 61. 小程序页面间传递数据的方法 62. 小程序为什么没有 DOM API 63. 微信小程序的优劣势 64. Node 和 Element 的关系 65. 页面生命周期事件:DOMContentLoaded、load、beforeunload、unload 66. 渐进增强与优雅降级 67. 渐进式 JPEG 图片格式 68. PV 和 UV 的区别 69. Script 标签 defer 和 async 70. 实现点击回到顶部功能 71. script 标签能否使用自闭合语法 72. src 与 href 的区别 73. SSG 静态网站生成 74. style 标签位置对页面渲染的影响 75. 从输入 URL 到页面显示的过程 76. Web 标准与可访问性理解 77. 网页常用图片格式 78. 网页常用图片格式 79. Web 标准与 W3C 标准 80. WebSocket 低版本浏览器兼容方案 81. Web Worker 的作用与场景 82. 微信小程序事件传值 83. 微信小程序文件结构 84. 微信小程序的架构 85. 微信小程序原理 86. 页面白屏时间优化 87. 小程序 WXSS 与 CSS 的区别 88. XHTML 与 HTML 的区别

HTML5 离线存储原理与使用

使用 Service Worker 和 Cache API 实现网页离线访问

问题

HTML5 的离线储存怎么使用,工作原理能不能解释一下?

解答

HTML5 离线存储有两种方案:

  1. Application Cache(已废弃)- 通过 manifest 文件声明缓存资源
  2. Service Worker + Cache API(推荐)- 现代离线存储方案

Application Cache(了解即可)

<!-- index.html -->
<!DOCTYPE html>
<html manifest="app.appcache">
<head>
  <title>离线应用</title>
</head>
<body>
  <h1>Hello Offline</h1>
</body>
</html>
# app.appcache
CACHE MANIFEST
# v1.0.0

# 需要缓存的文件
CACHE:
index.html
styles.css
app.js

# 需要网络访问的文件
NETWORK:
api/

# 离线时的替代页面
FALLBACK:
/ offline.html

⚠️ Application Cache 已被废弃,新项目请使用 Service Worker。

Service Worker + Cache API(推荐)

1. 注册 Service Worker

// main.js - 在页面中注册 Service Worker
if ('serviceWorker' in navigator) {
  navigator.serviceWorker.register('/sw.js')
    .then(registration => {
      console.log('SW 注册成功,作用域:', registration.scope);
    })
    .catch(error => {
      console.log('SW 注册失败:', error);
    });
}

2. 编写 Service Worker

// sw.js - Service Worker 文件
const CACHE_NAME = 'my-app-v1';
const CACHE_URLS = [
  '/',
  '/index.html',
  '/styles.css',
  '/app.js',
  '/images/logo.png'
];

// 安装阶段:缓存静态资源
self.addEventListener('install', event => {
  event.waitUntil(
    caches.open(CACHE_NAME)
      .then(cache => {
        console.log('缓存资源');
        return cache.addAll(CACHE_URLS);
      })
      .then(() => self.skipWaiting()) // 立即激活
  );
});

// 激活阶段:清理旧缓存
self.addEventListener('activate', event => {
  event.waitUntil(
    caches.keys().then(cacheNames => {
      return Promise.all(
        cacheNames
          .filter(name => name !== CACHE_NAME)
          .map(name => caches.delete(name))
      );
    }).then(() => self.clients.claim()) // 接管所有页面
  );
});

// 拦截请求:优先使用缓存
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) {
            return response;
          }
          // 克隆响应并缓存
          const responseClone = response.clone();
          caches.open(CACHE_NAME).then(cache => {
            cache.put(event.request, responseClone);
          });
          return response;
        });
      })
      .catch(() => {
        // 网络失败,返回离线页面
        return caches.match('/offline.html');
      })
  );
});

3. 常用缓存策略

// 策略1:缓存优先(Cache First)
async function cacheFirst(request) {
  const cached = await caches.match(request);
  return cached || fetch(request);
}

// 策略2:网络优先(Network First)
async function networkFirst(request) {
  try {
    const response = await fetch(request);
    const cache = await caches.open(CACHE_NAME);
    cache.put(request, response.clone());
    return response;
  } catch {
    return caches.match(request);
  }
}

// 策略3:仅缓存(Cache Only)
async function cacheOnly(request) {
  return caches.match(request);
}

// 策略4:仅网络(Network Only)
async function networkOnly(request) {
  return fetch(request);
}

// 策略5:Stale While Revalidate(返回缓存同时更新)
async function staleWhileRevalidate(request) {
  const cache = await caches.open(CACHE_NAME);
  const cached = await cache.match(request);
  
  const fetchPromise = fetch(request).then(response => {
    cache.put(request, response.clone());
    return response;
  });
  
  return cached || fetchPromise;
}

工作原理

┌─────────────┐     ┌─────────────────┐     ┌─────────────┐
│   浏览器     │ ──► │  Service Worker │ ──► │    网络     │
│   (页面)    │ ◄── │   (代理层)       │ ◄── │   (服务器)  │
└─────────────┘     └────────┬────────┘     └─────────────┘


                    ┌─────────────────┐
                    │   Cache Storage │
                    │    (本地缓存)    │
                    └─────────────────┘
  1. Service Worker 作为浏览器和网络之间的代理
  2. 拦截所有网络请求(fetch 事件)
  3. 根据缓存策略决定返回缓存还是发起网络请求
  4. 离线时直接返回缓存内容

关键点

  • Application Cache 已废弃,现代项目使用 Service Worker
  • Service Worker 运行在独立线程,不能直接操作 DOM
  • 必须在 HTTPS 环境下使用(localhost 除外)
  • Service Worker 有生命周期:install → waiting → activate → fetch
  • 根据资源类型选择合适的缓存策略:静态资源用缓存优先,API 用网络优先