面试题:浏览器是如何对 HTML5 的离线储存资源进行管理和加载的?

HTML5 的离线存储主要通过 Application Cache (AppCache)Service Worker 两种技术实现。需要注意的是,Application Cache 已被现代浏览器废弃,因其存在诸多设计缺陷,而 Service Worker 是当前推荐的、更强大和灵活的离线解决方案。

下面分别介绍这两种技术的管理与加载机制,并重点阐述现代标准 Service Worker


1. Application Cache (AppCache) – (已废弃,了解即可)

AppCache 使用一个 manifest 文件.appcache.manifest)来告诉浏览器哪些资源需要缓存。

管理和加载流程

  1. 首次访问
    • 浏览器解析 HTML,发现 <html manifest="cache.appcache"> 标签。
    • 浏览器请求并下载 cache.appcache 文件。
    • 解析 manifest 文件,下载其中列出的所有资源(HTML, CSS, JS, 图片等),并将其存储在浏览器的专用缓存中。
    • 页面正常加载并显示。
  2. 后续访问(在线)
    • 浏览器检查 manifest 文件是否有更新(通过文件内容变化,而非时间戳)。
    • 如果有更新:浏览器重新下载所有在 manifest 中列出的资源(即使只有一个文件变了,也会全部重新下载),更新缓存,然后使用新缓存加载页面。
    • 如果没有更新:直接从本地缓存加载所有资源,页面快速显示。
  3. 离线访问
    • 浏览器检测到网络不可用。
    • 自动从本地缓存中加载 manifest 文件中列出的所有资源。
    • 页面正常显示。

AppCache 的致命缺点(导致其被废弃)

  • 全量更新:manifest 文件中任何一处修改,都会导致所有资源重新下载,效率低下。
  • 首次加载后才缓存:用户第一次访问必须在线,无法立即离线使用。
  • 更新机制复杂且不透明:开发者难以精确控制更新时机。
  • 缓存清除困难:一旦缓存,很难清除,容易导致用户看到旧版本。
  • 安全性问题:存在潜在的安全风险。

结论:AppCache 已被 W3C 标准移除,不应在新项目中使用。


2. Service Worker – (现代标准,推荐)

Service Worker 是一个运行在浏览器后台的 JavaScript 脚本,独立于网页主线程。它充当网络请求的代理,可以拦截、处理和响应网络请求,从而实现强大的离线功能。

管理和加载流程

  1. 注册 (Registration)
    • 在主页面的 JavaScript 中(通常在 window.onloadDOMContentLoaded 事件后),调用 navigator.serviceWorker.register('/sw.js')
    • 浏览器下载 sw.js 文件,并在后台启动 Service Worker。
  2. 安装 (Install)
    • Service Worker 首次启动时,会触发 install 事件。
    • 开发者在 install 事件的监听器中,使用 Cache API 将关键资源(如首页 HTML、核心 CSS、JS、离线页面)预先缓存到浏览器的 Cache Storage 中。
    self.addEventListener('install', (event) => {
      event.waitUntil(
        caches.open('v1').then((cache) => {
          return cache.addAll([
            '/',
            '/styles/main.css',
            '/scripts/app.js',
            '/offline.html'
          ]);
        })
      );
    });
  3. 激活 (Activate)
    • 安装成功后,Service Worker 进入 activating 状态,然后触发 activate 事件。
    • activate 事件中,通常进行缓存清理,删除旧版本的缓存,避免占用过多空间。
    self.addEventListener('activate', (event) => {
      event.waitUntil(
        caches.keys().then((cacheNames) => {
          return Promise.all(
            cacheNames.filter((cacheName) => {
              return cacheName.startsWith('my-app-') && cacheName !== 'v1';
            }).map((cacheName) => {
              return caches.delete(cacheName);
            })
          );
        })
      );
    });
  4. 网络拦截与资源加载 (Fetch)
    • 激活后,Service Worker 开始拦截页面发出的所有网络请求(通过 fetch 事件)。
    • 开发者可以在此事件中编写自定义逻辑来决定资源从哪里加载:
      • 优先缓存:先检查 Cache Storage 中是否有请求的资源,如果有,直接返回缓存版本(最快)。
      • 网络回退:如果缓存中没有,则发起网络请求,获取资源后,可以将其存入缓存(供下次使用),然后返回给页面。
      • 离线策略:如果网络请求失败(离线),可以返回一个预缓存的“离线页面”(offline.html)或缓存中的降级资源。
    self.addEventListener('fetch', (event) => {
      event.respondWith(
        caches.match(event.request).then((response) => {
          // 如果缓存中有,返回缓存
          if (response) {
            return response;
          }
          // 否则,发起网络请求
          return fetch(event.request).then(
            (networkResponse) => {
              // 将网络响应克隆并存入缓存(可选)
              const responseToCache = networkResponse.clone();
              caches.open('v1').then((cache) => {
                cache.put(event.request, responseToCache);
              });
              return networkResponse;
            }
          ).catch(() => {
            // 网络请求失败,返回离线页面
            return caches.match('/offline.html');
          });
        })
      );
    });
  5. 更新
    • 当开发者更新了 sw.js 文件,浏览器会检测到文件内容变化(字节不同)。
    • 新的 Service Worker 会启动安装过程,但不会立即激活。
    • 当所有使用旧 Service Worker 的页面都被关闭后,新的 Service Worker 才会激活,接管网络请求。
    • 开发者可以在 installactivate 事件中精确控制新旧缓存的迁移和清理。

Service Worker 的优势

  • 精细控制:开发者可以完全控制缓存策略(缓存哪些、何时缓存、如何更新)。
  • 增量更新:可以只更新变化的资源,无需全量下载。
  • 离线优先:可以实现“离线优先”或“网络优先”等多种策略。
  • 后台同步:支持 Background Sync,在网络恢复后自动同步数据。
  • 推送通知:支持 Push API,实现消息推送。
  • 性能更好:避免了 AppCache 的许多性能陷阱。

总结

特性Application Cache (AppCache)Service Worker
状态已废弃现代标准,推荐使用
核心机制Manifest 文件列表JavaScript 脚本代理网络请求
缓存管理自动、全量、不灵活手动、增量、高度灵活
更新机制全量更新,不透明增量更新,开发者可控
离线加载直接从缓存加载 manifest 列表中的资源通过 fetch 事件拦截,按策略返回缓存或网络资源
首次加载必须在线可以设计为离线可用
功能仅限离线缓存离线、后台同步、消息推送、拦截路由等

结论:浏览器通过 Service Worker 对 HTML5 的离线资源进行管理。它通过注册、安装、激活三个生命周期阶段来安装和更新缓存,并通过拦截 fetch 事件来智能地决定资源是从本地缓存还是网络加载,从而实现强大、灵活且可靠的离线体验。开发者应使用 Service Worker 而非已废弃的 AppCache。

THE END
喜欢就支持一下吧
点赞13 分享