离线缓存实战总结:从原理到踩坑经验全解析

UX培静 前端 阅读 2,689
赞 44 收藏
二维码
手机扫码查看
反馈

项目初期的技术选型

最近接手了一个移动端的电商项目,需求是用户在网络状况不佳或完全无网络的情况下,也能流畅地浏览商品列表和详情页。这个需求听起来挺简单的,但实际上涉及到的技术点还挺多的。一开始我们考虑过用PWA(渐进式Web应用),但后来发现PWA的安装流程对用户来说有点复杂,而且我们的目标用户群体主要是中老年用户,他们可能不太愿意去安装一个网页应用。

离线缓存实战总结:从原理到踩坑经验全解析

于是我们就转向了离线缓存方案。离线缓存技术可以让我们在用户首次访问时,把一些静态资源(比如图片、CSS、JS等)缓存到用户的设备上,这样即使用户下次没有网络,也能从缓存中加载这些资源,提升用户体验。

开始动手:配置Service Worker

首先,我们需要配置一个Service Worker。Service Worker是一个在浏览器后台运行的脚本,它可以拦截和处理网络请求,实现离线缓存等功能。配置起来其实也不难,主要就是注册一个Service Worker,然后在Service Worker中写入缓存策略。

这里直接上代码:

// 注册Service Worker
if ('serviceWorker' in navigator) {
  window.addEventListener('load', () => {
    navigator.serviceWorker.register('/sw.js').then(registration => {
      console.log('Service Worker registered with scope:', registration.scope);
    }).catch(error => {
      console.error('Service Worker registration failed:', error);
    });
  });
}

然后在sw.js文件中,我们需要定义缓存策略:

const CACHE_NAME = 'my-cache-v1';
const urlsToCache = [
  '/',
  '/styles/main.css',
  '/scripts/app.js',
  '/images/logo.png'
];

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

self.addEventListener('fetch', event => {
  event.respondWith(
    caches.match(event.request).then(response => {
      if (response) {
        return response;
      }
      return fetch(event.request);
    })
  );
});

这段代码的意思是,在Service Worker安装时,会打开一个缓存,并将指定的URL资源添加到缓存中。在后续的请求中,如果缓存中有对应的资源,就直接返回缓存中的内容,否则再去网络请求。

最大的坑:动态数据的缓存

前面的静态资源缓存搞定了,但是我们还需要解决一个问题:如何缓存动态数据?比如商品列表和详情页的数据都是从后端API获取的,这些数据是动态变化的,不可能每次都缓存最新的数据。

为了解决这个问题,我们决定使用IndexedDB来存储动态数据。IndexedDB是一个在浏览器中运行的数据库,可以用来存储大量的结构化数据。

下面是我们在Service Worker中使用IndexedDB缓存动态数据的代码:

self.addEventListener('fetch', event => {
  const requestUrl = new URL(event.request.url);

  if (requestUrl.origin === 'https://jztheme.com' && requestUrl.pathname.startsWith('/api/')) {
    event.respondWith(
      (async () => {
        const db = await openDatabase();
        const transaction = db.transaction(['data'], 'readonly');
        const objectStore = transaction.objectStore('data');
        const data = await objectStore.get(requestUrl.href);

        if (data) {
          return new Response(JSON.stringify(data), {
            headers: { 'Content-Type': 'application/json' },
            status: 200
          });
        }

        const response = await fetch(event.request);
        if (response.ok) {
          const responseData = await response.json();
          const putRequest = objectStore.put(responseData, requestUrl.href);
          putRequest.onsuccess = () => {
            console.log('Data stored in IndexedDB');
          };
        }
        return response;
      })()
    );
  }
});

function openDatabase() {
  return new Promise((resolve, reject) => {
    const request = indexedDB.open('my-database', 1);

    request.onupgradeneeded = event => {
      const db = event.target.result;
      const objectStore = db.createObjectStore('data', { keyPath: 'url' });
      objectStore.createIndex('by_url', 'url', { unique: true });
    };

    request.onsuccess = event => {
      resolve(event.target.result);
    };

    request.onerror = event => {
      reject(event.target.error);
    };
  });
}

这段代码的核心逻辑是:当请求的是API数据时,先从IndexedDB中查找是否有缓存的数据,如果有就直接返回;如果没有,则发起网络请求,请求成功后将数据存入IndexedDB,并返回响应。

说实话,这部分代码折腾了我好几天,主要是因为IndexedDB的API实在是有点复杂,而且调试起来也比较麻烦。不过好在最后还是解决了问题,动态数据也能被缓存下来了。

最终的解决方案

经过一番努力,我们终于实现了离线缓存功能。用户在第一次访问时,会把静态资源和动态数据都缓存到本地,即使下次没有网络,也能正常浏览商品列表和详情页。

不过,这个方案也不是完美的。比如,如果用户长时间不清理缓存,可能会导致缓存的数据过期,影响用户体验。此外,IndexedDB的性能在某些低端设备上也可能会有问题。不过这些问题目前对我们来说影响不大,暂时可以接受。

回顾与反思

通过这次项目,我对离线缓存有了更深入的理解。虽然过程中遇到了不少坑,但通过不断尝试和调整,最终还是找到了一个相对可行的解决方案。离线缓存技术确实能显著提升用户体验,特别是在网络不稳定的情况下。

当然,这个方案还有很多可以优化的地方,比如可以增加缓存版本控制,确保用户每次都能获取到最新的数据;还可以优化IndexedDB的性能,减少低端设备上的卡顿问题。

以上是我的项目经验,希望对你有帮助,欢迎交流!

本文章不代表JZTHEME立场,仅为作者个人观点 / 研究心得 / 经验分享,旨在交流探讨,供读者参考。
发表评论
技术丹丹
读完这篇文章,我在写代码时更加注重细节,出错率明显降低。
点赞 4
2026-02-10 13:25