五、浏览器缓存 一、Cache Storage缓存存储1. 概念Cache Storage缓存存储是浏览器提供的一种持久化存储机制它专门用来缓存网络请求和响应比如 HTML、CSS、JS 文件、图片等。它通常与Service Worker后台线程配合使用当用户访问网页时Service Worker 会拦截网络请求优先从这个“仓库”里拿资源从而让网页实现秒开甚至在断网状态下依然能访问。与 Web Storage 和 IndexedDB 不同Cache Storage 的设计目标不是存储自定义数据而是完整地缓存 HTTP 请求/响应对这使得它成为构建离线应用PWA的核心技术。2. 特征网络请求级缓存它不是简单地存数据而是把完整的 HTTP 请求Request和响应Response打包存起来形成一一对应的映射关系。异步操作所有的 API 都返回 Promise不会阻塞主线程保证网页的流畅度。即使缓存大量资源也不会影响页面渲染。多缓存隔离支持创建多个独立命名的缓存仓库例如v1-cache、v2-cache、image-cache方便进行版本控制和资源分类管理。有存储上限浏览器通常会限制每个网站的缓存空间大约在 50MB 到 1GB 之间具体取决于浏览器和设备的可用磁盘空间所以需要定期清理旧版本的缓存避免超出配额。3. 使用场景在实际开发中Cache Storage 主要用于构建高性能的PWA渐进式 Web 应用使用场景说明典型示例关键资源预缓存在网页首次加载时把核心的 CSS、JS 和 Logo 图片提前下载并存入缓存。下次打开瞬间加载完毕。应用外壳App Shell、基础样式表离线访问Offline First当用户在没有网络如地铁上时通过 Service Worker 拦截请求直接返回缓存的页面让用户依然能看到网页的基本结构。离线阅读、离线文档动态内容缓存比如博客文章用户看过一次后将其缓存在本地下次再看时不仅秒开还不消耗服务器流量。已读文章缓存、API 响应缓存4. 怎么用举例Cache Storage 通过全局的caches对象来操作。它的核心逻辑通常写在 Service Worker 中。下面演示三个最常用的场景。 场景一安装时预缓存核心资源当 Service Worker 被激活安装时我们提前把重要文件存进缓存// sw.js (Service Worker 文件)self.addEventListener(install,event{event.waitUntil(// 1. 打开或创建一个名为 my-site-v1 的缓存仓库caches.open(my-site-v1).then(cache{// 2. 批量添加需要缓存的资源returncache.addAll([/styles.css,/main.js,/logo.png]);}));}); 场景二拦截网络请求离线优先策略这是最核心的魔法当浏览器发起任何请求时我们拦截它先去缓存里找找不到再去请求网络// sw.jsself.addEventListener(fetch,event{event.respondWith(// 1. 尝试在缓存中匹配当前的请求caches.match(event.request).then(response{// 2. 如果缓存里有直接返回缓存速度极快且支持离线if(response){returnresponse;}// 3. 如果缓存里没有再去向服务器发起真实的网络请求returnfetch(event.request);}));});小提示上述策略是「缓存优先Cache First」你也可以根据需求改为「网络优先Network First」或「仅缓存Cache Only」等策略灵活适配不同场景。 场景三清理旧版本缓存版本控制当你的网站更新了比如发布了 v2 版本你需要清理掉旧的 v1 缓存防止用户看到旧页面// sw.jsconstCURRENT_CACHEmy-site-v2;// 定义当前最新版本self.addEventListener(activate,event{event.waitUntil(caches.keys().then(keys{// 遍历所有缓存删除不是当前版本的旧缓存returnPromise.all(keys.filter(keykey!CURRENT_CACHE).map(keycaches.delete(key)));}));});最佳实践每次发布新版本时更新CURRENT_CACHE的名称如my-site-v3并在activate事件中删除旧版本缓存确保用户始终使用最新的资源同时避免缓存空间无限二、Service Worker1. 什么是 Service Worker简单来说Service Worker 是一段运行在浏览器后台的 JavaScript 脚本。通俗理解传统的网页就像一个只会等快递的人网断了快递数据送不到网页就白屏了。而 Service Worker 就是你雇的一个“管家”他站在你的房子浏览器和外面的世界网络之间。他独立工作不会把你家里的主线任务网页主线程卡住。他不能直接动你家里的家具无法访问 DOM。但他可以决定快递怎么送如果外面下雨断网管家可以直接从家里的仓库缓存把以前存好的东西拿出来给你用。专业定义它是 Web 应用、浏览器和网络之间的一个代理服务器。它基于事件驱动能够拦截网络请求并根据网络情况决定如何处理这些请求。2. 作用是什么这个“管家”能帮你做很多事主要有以下几点作用说明离线访问最核心的作用当用户在地铁、电梯等没网的地方打开你的网站时管家可以直接把之前存好的页面拿出来给用户看而不是显示“无网络连接”。性能加速秒开体验管家可以把网站的图片、CSS、JS 等静态资源存在本地。下次用户再访问时直接从本地读取速度极快大大减少等待时间。后台推送通知哪怕用户把你的网页关了只要服务器有新消息管家依然可以接收到并在用户的手机或电脑右下角弹出通知提醒。后台同步比如用户在没网的时候写了一篇博客并点击了“发布”。管家会先把这篇博客暂存起来等用户手机连上 WiFi 后管家再自动帮他把博客发到服务器上保证数据不丢。3. 使用场景在实际开发中我们通常在以下场景会请这位“管家”出山使用场景说明渐进式 Web 应用PWA你想把网页做得像手机 App 一样能离线用、能安装到桌面那就必须用 Service Worker。内容阅读类网站比如新闻网站、博客、文档。这类网站内容一旦加载用户通常只是看不需要频繁请求服务器。用 Service Worker 缓存后断网也能看历史文章。电商或工具类应用比如购物车、待办事项Todo List。用户在弱网下把商品加入购物车Service Worker 可以在后台默默同步提升用户体验。4. 基本用法使用 Service Worker 就像雇佣管家分为两步“签合同注册” 和 “给管家定规矩编写脚本”。第一步在网页中注册签合同在你的主 JS 文件比如app.js中告诉浏览器去加载管家// 检查浏览器是否支持if(serviceWorkerinnavigator){// 注册管家指定他的工作范围比如整个网站navigator.serviceWorker.register(/sw.js).then(function(registration){console.log(管家注册成功工作范围:,registration.scope);}).catch(function(error){console.log(管家注册失败:,error);});}第二步编写 sw.js 文件给管家定规矩在这个文件里我们要告诉管家怎么处理快递网络请求// 1. 安装阶段管家刚上任先把常用的东西放进仓库self.addEventListener(install,function(event){event.waitUntil(caches.open(my-cache-v1).then(function(cache){// 把首页和核心样式表先存起来returncache.addAll([/,/styles/main.css]);}));});// 2. 拦截请求阶段管家开始处理每一次网络请求self.addEventListener(fetch,function(event){event.respondWith(// 先去仓库找找有没有这个资源caches.match(event.request).then(function(response){// 如果找到了直接返回缓存秒开if(response){returnresponse;}// 如果没找到再去网络上请求returnfetch(event.request);}));});避坑小贴士安全要求因为管家权力太大能拦截所有请求为了防止黑客做坏事Service Worker 只能在 HTTPS 环境下运行本地开发localhost除外。不能操作 DOM千万别在sw.js里写document.getElementById()这会报错的。他只能管网络请求和缓存。生命周期Service Worker 有自己的生命周期安装 → 激活 → 运行。当页面关闭后它不会立即消失而是进入空闲状态在需要时被唤醒长时间不用后会被浏览器终止以节省资源。膨胀。三、self 监听事件在前端开发中self代表的是 Service Worker 的全局作用域ServiceWorkerGlobalScope。在这个作用域里我们可以通过self.addEventListener来监听各种事件。为了方便你记忆我们可以把这些事件分为**“生命周期事件和功能事件”**两大类1. 生命周期事件管家的入职与转正这类事件用于管理 Service Worker 的安装、激活和更新确保应用平稳过渡。install安装事件触发时机Service Worker 首次注册或检测到新版本时触发且只触发一次。实际开发场景这是管家刚入职的时候。我们通常在这个阶段把网站最核心的资源比如 HTML、核心 CSS、Logo 图片等提前下载到本地缓存里这叫预缓存Pre-cache确保用户首次访问或断网时能看到基础页面。activate激活事件触发时机安装完成后准备正式接管页面控制权时触发。实际开发场景这是管家正式转正的时刻。这时候非常适合用来清理旧版本的缓存。比如你更新了网站旧缓存没用了就可以在这里把它们删掉防止占用用户手机内存也避免新旧缓存冲突。2. 功能事件管家的日常工作这类事件是 Service Worker 真正干活的地方用于处理网络请求、消息推送等。fetch网络请求拦截事件触发时机只要当前作用域内的页面发起了任何网络请求比如加载图片、请求接口数据就会触发。实际开发场景这是管家最核心的工作。你可以决定这个请求是直接发给服务器还是从本地缓存里拿甚至返回一段自定义的假数据。message消息通信事件触发时机当网页主线程你的普通 JS 文件主动给 Service Worker 发消息时触发。实际开发场景因为 Service Worker 无法访问 DOM如果网页想告诉管家一些事情比如用户点击了某个按钮请帮我记录一下就可以通过postMessage发消息管家在message事件里接收并处理。push推送通知事件触发时机当服务器通过推送服务如 FCM向浏览器发送消息时触发即使网页没打开也能触发。实际开发场景实现类似微信或淘宝的消息提醒。收到推送后管家可以调用showNotification在用户的电脑右下角或手机通知栏弹出一个通知。notificationclick通知点击事件触发时机用户点击了上面弹出的系统通知时触发。实际开发场景用户点击通知后你可以控制是打开一个新网页还是直接跳转到 App 里的某个具体页面。sync后台同步事件触发时机当网络恢复稳定且之前注册过同步任务时触发。实际开发场景用户在地铁里没网发了一条朋友圈管家先把它存在本地并注册一个sync任务。等用户出了地铁连上 WiFi管家会自动触发这个事件把那条朋友圈发到服务器用户完全无感知。小贴士在install和activate这两个生命周期事件里通常会包含异步操作比如读写缓存。这时候一定要记得用event.waitUntil()把异步代码包起来告诉浏览器“我还有活没干完先别急着进入下一步”四、event 属性方法event 属性Properties表格汇总属性说明示例event.request属于fetch事件。返回被拦截的网络请求对象包含 URL、请求方法和 HTTP 头等信息。event.request.url获取请求的 URL 地址event.data属于message或push事件。获取网页主线程传过来的数据或服务器推送的消息内容。event.data.json()解析推送的 JSON 数据event.tag属于sync事件。返回当前后台同步任务的标签标识符用于区分不同的同步任务。if (event.tag sync-messages)判断是否为同步消息任务event.lastChance属于sync事件。布尔值。如果浏览器在当前尝试之后不再进行进一步的同步尝试返回true。if (event.lastChance) { /* 执行最后挣扎逻辑 */ }event.notification属于notificationclick/close事件。返回被点击或关闭的系统通知对象。event.notification.title获取通知的标题event.action属于notificationclick事件。返回用户点击的通知按钮的字符串 ID。如果点击通知本体则为空字符串。if (event.action reply) { /* 执行回复逻辑 */ }event.source属于message事件。返回发送这个消息的客户端Client对象的引用。event.source.postMessage(收到)向发送方回传消息event 方法Methods表格汇总方法说明示例event.waitUntil(promise)属于install、activate、push、sync等事件。告诉浏览器我还有后台任务没干完你先别急着往下走或关闭。event.waitUntil(caches.open(v1))等待缓存打开完成event.respondWith(response)属于fetch事件。拦截请求并返回一个自定义的响应Response 对象或 Promise。event.respondWith(caches.match(event.request))从缓存中返回响应五、caches 方法在前端开发中caches是一个全局对象类型为CacheStorage它本身没有具体的属性但提供了管理缓存空间的方法。通过它我们可以打开具体的Cache缓存实例对象并对里面的资源进行操作。仓库管理CacheStorage 级别的方法用于管理整个缓存空间建库、查库、删库。方法说明示例caches.open(cacheName)打开指定名称的缓存空间。如果该缓存不存在浏览器会自动创建一个新的并返回。caches.open(v1-assets)打开名为 v1-assets 的缓存caches.keys()返回一个 Promise解析为当前域名下所有缓存名称的数组。常用于清理旧版本缓存。caches.keys().then(keys console.log(keys))打印所有缓存名caches.has(cacheName)检查是否存在指定名称的缓存。返回一个布尔值的 Promise。caches.has(v1-assets)判断该缓存是否存在caches.delete(cacheName)删除指定名称的整个缓存空间及其内部的所有数据。caches.delete(old-cache)删除旧缓存物品操作Cache 级别的方法通过caches.open()获取到具体的cache对象后用于对缓存内的具体资源请求-响应对进行增、删、改、查。方法说明示例cache.match(request)在指定的缓存中查找匹配的第一个请求返回对应的响应Response或undefined。cache.match(event.request)在缓存中查找当前请求cache.matchAll(request?)匹配并返回所有符合条件的响应数组。如果不传参数则返回该缓存中的所有响应。cache.matchAll(/api/)获取所有包含 /api/ 的缓存响应cache.add(request)自动发起网络请求获取指定 URL 的资源并将其存入缓存仅支持 GET 请求。cache.add(/images/logo.png)抓取并缓存 Logocache.addAll(requests)批量抓取多个请求并缓存原子操作只要有一个失败整个操作就会失败。常用于install阶段。cache.addAll([/, /style.css])批量缓存核心资源cache.put(request, response)手动将一个请求和对应的响应对象直接存入缓存。如果已存在则覆盖更新。cache.put(req, res.clone())将克隆的响应存入缓存cache.delete(request)从缓存中删除匹配的请求-响应对。删除成功返回true未找到返回false。cache.delete(/old-page.html)删除旧页面的缓存cache.keys(request?)返回该缓存中所有或匹配的请求对象Request的数组。cache.keys().then(reqs console.log(reqs))列出缓存的所有请求避坑小贴士add vs putadd是懒人模式它会自动去网络抓取资源并存入缓存而put是全控模式你需要自己准备好 Request 和 Response 对象通常在拦截fetch事件时使用。响应流只能读一次当你从网络拿到一个 response 时如果既要返回给页面又要用put存进缓存必须使用response.clone()克隆一份否则会导致报错