公众号:小博的前端笔记
核心原则:浏览器总是优先尝试使用最快、最接近的可用有效缓存,避免不必要的网络请求。
以下是浏览器缓存机制的详细优先级顺序(从最高到最低),以及关键细节:
1、Service Worker 缓存 (最高优先级)
-
触发条件: 网站注册了 Service Worker,并且该 Service Worker 的
fetch
事件处理程序中有意拦截请求并返回缓存响应。 -
机制:
- Service Worker 作为浏览器和网络之间的代理运行。
- 在
fetch
事件中,你可以完全编程控制 如何响应请求:可以返回CacheStorage
(caches API) 中的缓存、发起网络请求、构造新响应,甚至返回占位符。 - 策略完全自定义: 你可以实现
CacheFirst
(优先缓存)、NetworkFirst
(优先网络)、StaleWhileRevalidate
(快速返回缓存后更新)、NetworkOnly
(仅网络)等任何策略。
-
优先级最高原因: Service Worker 能拦截所有 同源下的请求(包括页面本身、HTML、CSS、JS、图片、API 请求等)。它的
fetch
事件处理程序最先执行,如果它决定从自己的缓存中返回响应,浏览器将不会进行后续任何缓存检查或网络请求。 -
面试要点: 强调其编程可控性 和最高拦截权。即使资源在 HTTP 缓存中过期,只要 Service Worker 决定返回缓存版本,浏览器就会使用它。它是实现离线应用、高级缓存策略(如后台更新)的核心。
2、Memory Cache (内存缓存)
-
触发条件: 当前浏览会话(Tab 页)中已加载过的资源(如脚本、样式、图片)。通常是同一页面或之前页面加载的资源被再次请求。
-
机制:
- 资源被直接存储在系统内存 (RAM) 中。
- 访问速度极快(比磁盘快几个数量级)。
- 生命周期非常短:关闭 Tab 页通常会被清除。浏览器在内存紧张时也会主动清理。
- 通常用于存储较小 且高频访问的资源(如当前页面引用的 JS、CSS、小图标)。大的图片/视频可能不会被放入内存缓存,或优先被清除。
-
优先级原因: 速度最快。如果资源在内存中存在且有效(未过期),浏览器会毫不犹豫地使用它。
-
面试要点: 强调速度优势 和临时性 。它是提升页面二次加载速度(尤其是刷新)的关键。开发者无法直接控制哪些资源进入内存缓存。
3、HTTP Cache (Disk Cache / 磁盘缓存)
-
触发条件: 资源响应头中包含有效的缓存指令(如
Cache-Control
,Expires
,ETag
,Last-Modified
)。 -
机制: 这是最核心、最常用、开发者可控度最高的缓存机制。
-
资源被存储在硬盘上。
-
容量大,生命周期长(按策略,可能跨会话、跨 Tab)。
-
强缓存 (Freshness):
- 浏览器检查
Cache-Control
(优先级高) 和Expires
头部,判断资源是否在有效期内 (max-age
,s-maxage
)。 - 如果有效期内 (
fresh
),浏览器直接从磁盘加载资源,完全不发送任何网络请求! (状态码 200from disk cache
/from prefetch cache
)。
- 浏览器检查
-
协商缓存 (Validation):
-
如果强缓存失效 (
stale
),浏览器会发起一个条件请求 (Conditional Request) 。 -
请求头带上之前响应中的
ETag
值 (If-None-Match
) 或Last-Modified
时间 (If-Modified-Since
)。 -
服务器检查资源:
- 如果未修改:返回
304 Not Modified
(空 body),浏览器从 HTTP 缓存加载资源 (状态码 200from disk cache
或304
)。 - 如果已修改:返回
200 OK
和完整的新资源,浏览器使用新资源并更新缓存。
- 如果未修改:返回
-
-
关键头部:
Cache-Control
: 核心指令 (max-age=3600
,no-cache
,no-store
,public
,private
,immutable
,must-revalidate
,stale-while-revalidate
等)。Expires
(HTTP/1.0,被Cache-Control
覆盖):绝对过期时间。ETag
/Last-Modified
: 用于协商缓存验证。Vary
: 指定缓存变体(如按User-Agent
,Accept-Encoding
缓存不同版本)。
-
-
优先级原因: 速度较快(比网络快),容量大,持久性强,是开发者主要优化的目标。当内存缓存没有命中(比如首次访问、内存已清除),浏览器就会检查 HTTP 缓存。
-
面试要点: 这是必考重点 !必须清晰区分强缓存 (不发请求)和协商缓存 (发条件请求,可能返回 304)。深入理解
Cache-Control
常用指令的含义和组合。强调no-store
(完全不缓存) 和no-cache
(总是验证) 的区别。解释ETag
(通常更精确) 和Last-Modified
的优缺点。
4、Push Cache (HTTP/2 Server Push 缓存) (最低优先级且特殊)
-
触发条件: 服务器使用 HTTP/2 的 Server Push 功能主动推送资源给浏览器。
-
机制:
- 资源在服务器主动推送时被临时存储。
- 生命周期极短 :通常只存在于当前 HTTP/2 连接期间。关闭连接或打开新连接都会失效。
- 同源限制宽松:即使跨域推送的资源也可能被存储(但遵循同源策略使用)。
- 非常规获取方式: 资源必须是被 Server Push 推下来的,不能通过常规的
GET
请求去"读取" Push Cache。 - 低优先级: 当浏览器需要某个资源时,如果它恰好在 Push Cache 中且有效(连接未关闭),可能会使用它。但它非常脆弱,通常作为优化手段(减少关键资源 RTT)。
-
优先级原因: 存在性依赖特殊条件(HTTP/2 + Server Push),且生命周期短。如果其他缓存(Memory/HTTP)有有效副本,它们会被优先使用。Push Cache 是最后一道防线。
-
面试要点: 说明其特殊性和临时性。知道它是 HTTP/2 的优化特性,但在实际缓存决策中作用有限且优先级最低。不是所有浏览器都支持或实现完善。
关键决策流程与优先级体现 (浏览器视角):
-
请求发起: 浏览器需要加载一个资源 (URL)。
-
Service Worker 拦截?
-
是: 执行 Service Worker 的
fetch
事件处理程序。- 如果处理程序返回了缓存响应 (无论来自 CacheStorage 还是其他地方):直接使用该响应 ,流程结束。 (最高优先级体现)
- 如果处理程序决定忽略缓存并直接
fetch()
网络请求:则流程继续向下。
-
-
Memory Cache 检查?
- 浏览器检查当前内存中是否有该资源的有效副本。
- 是且有效: 直接使用内存缓存 ,流程结束。 (次高优先级体现)
- 否 或 无效: 流程继续。
-
HTTP Cache (Disk Cache) 检查?
-
浏览器检查硬盘上是否有该资源的缓存副本及其有效性。
-
强缓存有效 (fresh): 直接使用磁盘缓存 ,不发任何请求,流程结束。 (核心缓存层)
-
强缓存失效 (stale) 但有验证器 (ETag/Last-Modified):
- 浏览器发起一个带验证头 (
If-None-Match
/If-Modified-Since
) 的条件请求到服务器。 - 服务器响应
304 Not Modified
:浏览器使用磁盘缓存中(已失效但未变)的副本 ,流程结束。 (协商缓存成功) - 服务器响应
200 OK
(新内容):浏览器使用新响应,更新 HTTP 缓存,流程结束。
- 浏览器发起一个带验证头 (
-
无缓存 或 缓存无效且无验证器 或
no-store
: 流程继续。
-
-
Push Cache 检查?(如果存在 HTTP/2 连接且之前有推送)
- 浏览器检查当前连接关联的 Push Cache 中是否有该资源。
- 是且连接有效: 可能 会使用它(但非常不可靠,优先级最低)。 (最低优先级体现)
- 否 或 无效: 流程继续。
-
网络请求:
- 以上所有缓存层均未提供有效响应。
- 浏览器发起一个常规的
GET
请求到服务器。 - 服务器返回响应 (
200 OK
)。 - 浏览器根据响应头 (
Cache-Control
等) 决定是否以及如何缓存这个新响应(存入 HTTP Cache / Memory Cache)。 - 使用响应内容。
面试回答技巧与要点总结:
-
结构化阐述: 按优先级顺序清晰列出层级:Service Worker -> Memory Cache -> HTTP Cache (强缓存 -> 协商缓存) -> Push Cache -> Network。
-
强调关键点:
- Service Worker 的绝对控制权:它最先拦截,可以决定一切。
- Memory Cache 的速度优势与临时性。
- HTTP Cache 是核心战场 :深入解释
Cache-Control
(max-age
,no-cache
,no-store
)、强缓存(不发请求)与协商缓存(发条件请求,304 vs 200)的本质区别。这是面试官最可能深挖的地方。 - Push Cache 的特殊性和低优先级。
-
解释流程: 简述浏览器决策链:"浏览器会依次检查 Service Worker 是否拦截并返回缓存 -> 检查内存缓存是否有效 -> 检查 HTTP 强缓存是否有效 -> 如果强缓存失效则发条件请求检查协商缓存 -> 最后尝试 Push Cache -> 都失败才发网络请求"。
-
举例说明: 可以结合常见指令举例:
- "
Cache-Control: max-age=3600, public
表示资源可以被任何缓存(浏览器、CDN)存储,并且在 3600 秒内,浏览器会直接从内存或磁盘加载它,不发请求(强缓存)。" - "
Cache-Control: no-cache
不表示不缓存,而是每次使用前必须 向服务器验证(发带ETag
/Last-Modified
的条件请求),如果服务器返回 304 则用缓存(协商缓存)。" - "
Cache-Control: no-store
才是真正的不缓存,每次都必须从网络获取。"
- "
-
提到启发式缓存: 如果响应没有明确指定
Expires
或Cache-Control: max-age
,但提供了Last-Modified
,浏览器可能 会根据Last-Modified
和当前时间的差值(通常取该差值的 10% 作为max-age
)进行启发式缓存。这是一个常被忽略但重要的细节。 -
清晰区分状态码/来源:
200 OK (from memory cache)
/200 OK (from disk cache)
:强缓存命中。304 Not Modified
:协商缓存验证成功(服务器未修改)。200 OK
(无特殊标注):来自网络请求。
-
结合实际优化: 最后可以提一下理解这些对性能优化的意义:如何设置合理的缓存策略减少请求、加快加载速度(静态资源长缓存+版本控制/指纹、API 谨慎缓存等)。
5、Service Worker 缓存是什么 如何实现
Service Worker 缓存是一种由 JavaScript 编程控制 的浏览器缓存机制,它是实现离线应用、后台同步、推送通知 以及高级缓存策略 的核心技术。它赋予了开发者对网络请求和响应的精细控制权,使其优先级凌驾于传统的 HTTP 缓存和内存缓存之上。
核心概念:
-
独立的 JavaScript Worker:
- Service Worker 是一个运行在浏览器后台的独立 JavaScript 线程。
- 它与主页面 JavaScript(运行在 Window 上下文)完全隔离,不能直接访问 DOM。
- 通过
postMessage
API 与页面进行通信。
-
代理网络请求:
- Service Worker 的核心功能是充当位于浏览器和网络之间的代理。
- 它可以拦截、修改、响应其作用域 (
scope
) 内的所有网络请求(包括页面导航请求、HTML、CSS、JS、图片、API 调用等)。
-
事件驱动:
-
其生命周期和行为由一系列事件驱动:
install
: Service Worker 首次安装或更新时触发。通常在此事件中进行资源预缓存。activate
: Service Worker 被激活,开始控制其作用域内的页面时触发。通常在此事件中进行旧缓存清理。fetch
: 每当作用域内的页面发起网络请求时触发。开发者可以在此事件中拦截请求并决定如何响应(从缓存返回、转发到网络、构造新响应等)。message
: 用于接收来自页面或其他 Service Worker 的消息。sync
/push
: 用于后台同步和推送通知(需要额外配置)。
-
-
作用域 (
scope
):- 定义 Service Worker 可以控制哪些页面。默认是其脚本文件所在的目录及其子目录。
- 注册时可以通过
scope
选项指定更宽或更窄的范围(必须在脚本文件所在路径下)。
-
缓存存储 (
CacheStorage
/caches
):- Service Worker 使用
CacheStorage
API(通过全局的caches
对象访问)来存储和管理缓存。 - 缓存存储在
Cache
对象中,每个Cache
可以存储大量的Request
/Response
对。 - 开发者可以创建多个命名缓存(如
v1-static-assets
,v2-api-responses
),便于管理和版本控制。
- Service Worker 使用
-
生命周期管理:
- 安装 (Installing): 下载并解析 Service Worker 脚本。
- 等待 (Waiting): 如果当前有活动的旧版 Service Worker 控制着页面,新安装的 Service Worker 会进入此状态,等待接管(除非调用
skipWaiting()
)。 - 激活 (Activating): 新 Service Worker 获得控制权,触发
activate
事件。旧 Service Worker 及其控制的页面将被停用。 - 活动 (Activated): 控制页面,处理
fetch
等事件。 - 冗余 (Redundant): 被新版本替换或安装失败。
如何实现 Service Worker 缓存 (核心步骤):
-
注册 Service Worker:
- 在主页面 JavaScript 中注册 Service Worker 脚本文件 (
sw.js
)。
javascript// main.js (或你的入口脚本) if ('serviceWorker' in navigator) { window.addEventListener('load', function() { navigator.serviceWorker.register('/sw.js', { scope: '/' }) // 注册 sw.js,作用域为根目录 '/' .then(function(registration) { console.log('ServiceWorker 注册成功,作用域: ', registration.scope); }) .catch(function(error) { console.log('ServiceWorker 注册失败: ', error); }); }); }
- 在主页面 JavaScript 中注册 Service Worker 脚本文件 (
-
编写 Service Worker 脚本 (
sw.js
) - 预缓存 (在install
事件中):- 在
install
事件中,打开一个缓存,并将需要预加载的关键静态资源(确保应用外壳能离线工作)添加到缓存中。 - 使用
event.waitUntil()
确保安装过程直到缓存操作完成才结束。
javascript// sw.js const CACHE_NAME = 'my-site-cache-v1'; // 使用版本号便于后续更新 const urlsToCache = [ '/', '/index.html', '/styles/main.css', '/scripts/main.js', '/images/logo.png' ]; self.addEventListener('install', function(event) { // 执行安装步骤:创建缓存并添加资源 event.waitUntil( caches.open(CACHE_NAME) .then(function(cache) { console.log('已打开缓存'); return cache.addAll(urlsToCache); // 缓存指定资源列表 }) ); });
- 在
-
编写 Service Worker 脚本 (
sw.js
) - 清理旧缓存 (在activate
事件中):- 在
activate
事件中,清理掉不属于当前版本的旧缓存。 - 使用
event.waitUntil()
确保清理操作完成。
javascriptself.addEventListener('activate', function(event) { const cacheWhitelist = [CACHE_NAME]; // 保留当前版本缓存 event.waitUntil( caches.keys().then(function(cacheNames) { return Promise.all( cacheNames.map(function(cacheName) { if (cacheWhitelist.indexOf(cacheName) === -1) { // 不在白名单中的缓存 return caches.delete(cacheName); // 删除旧缓存 } }) ); }) ); });
- 在
-
编写 Service Worker 脚本 (
sw.js
) - 拦截请求 & 响应 (在fetch
事件中):- 这是实现缓存策略的核心!
fetch
事件允许你拦截所有作用域内的请求。 - 你可以实现各种策略来决定如何响应请求。以下是一些常见策略的代码示例:
策略 1: 缓存优先 (Cache First - 适合静态资源)
javascriptself.addEventListener('fetch', function(event) { event.respondWith( caches.match(event.request) // 尝试在缓存中查找匹配的请求 .then(function(response) { // 缓存命中,返回缓存响应 if (response) { return response; } // 缓存未命中,发起网络请求 return fetch(event.request) .then(function(networkResponse) { // 可选:将新获取的资源添加到缓存中 (动态缓存) // 注意:通常只缓存 GET 请求且成功的响应 (200) if (event.request.method === 'GET' && networkResponse.status === 200) { const responseToCache = networkResponse.clone(); // 克隆响应(流只能读取一次) caches.open(CACHE_NAME) .then(function(cache) { cache.put(event.request, responseToCache); }); } return networkResponse; }); }) ); });
策略 2: 网络优先 (Network First - 适合需要最新数据的请求)
lessself.addEventListener('fetch', function(event) { event.respondWith( fetch(event.request) // 优先尝试网络请求 .then(function(networkResponse) { // 网络请求成功,返回响应并可选更新缓存 if (event.request.method === 'GET' && networkResponse.status === 200) { const responseToCache = networkResponse.clone(); caches.open(CACHE_NAME) .then(function(cache) { cache.put(event.request, responseToCache); }); } return networkResponse; }) .catch(function(error) { // 网络请求失败,尝试从缓存中获取 return caches.match(event.request); }) ); });
策略 3: 仅缓存 (Cache Only) / 仅网络 (Network Only) / 更新后重新验证 (Stale-While-Revalidate)
- 这些策略的实现逻辑类似,根据需求调整
caches.match
和fetch
的顺序以及处理逻辑即可。
- 这是实现缓存策略的核心!
-
处理更新:
-
当用户访问页面时,浏览器会自动检查
sw.js
是否有更新(字节比对)。 -
如果有更新,会下载新脚本并触发新 Service Worker 的
install
事件(旧 Service Worker 仍在运行)。 -
新 Service Worker 默认进入
waiting
状态,直到所有被旧 Service Worker 控制的页面都关闭(或用户强制刷新)。 -
为了强制新 Service Worker 立即接管 ,可以在新 Service Worker 的
install
事件中调用self.skipWaiting()
:luaself.addEventListener('install', function(event) { self.skipWaiting(); // 强制新 SW 立即激活 event.waitUntil(...); });
-
在
activate
事件中清理旧缓存(如前面所示)。
-
关键注意事项 & 最佳实践:
-
HTTPS 或 localhost: Service Worker 只能在 HTTPS 网站或
localhost
上运行(开发环境),这是出于安全考虑。 -
作用域 (
scope
): 确保scope
设置正确。Service Worker 只能控制其脚本文件所在路径及其子路径下的页面。 -
缓存策略选择: 根据资源类型选择合适的策略:
- 静态资源(CSS, JS, 图片等): 通常使用 缓存优先 (Cache First) + 长
max-age
HTTP 缓存 + 文件名哈希(内容指纹)实现"永久缓存"。Service Worker 确保即使 HTTP 缓存过期也能离线使用。 - 需要频繁更新的数据(API 响应、用户内容): 通常使用 网络优先 (Network First) 或 更新后重新验证 (Stale-While-Revalidate) ,优先获取最新数据,同时提供缓存作为后备或加速。
- 关键页面(HTML): 策略需谨慎。网络优先 + 缓存后备能保证内容更新,但也可能造成离线不可用。缓存优先能保证离线可用,但可能展示旧内容。通常结合应用外壳 (App Shell) 模型。
- 静态资源(CSS, JS, 图片等): 通常使用 缓存优先 (Cache First) + 长
-
缓存管理:
- 版本控制: 每次更新静态资源或 Service Worker 逻辑时,务必更新缓存名称 (
CACHE_NAME
) ,并在activate
事件中清理旧缓存。 - 限制缓存大小: Service Worker 缓存没有自动大小限制(不同于 HTTP 缓存)。需要自行实现逻辑(如
caches.keys()
+cache.entries()
计算大小,删除最旧/最少使用的缓存项)。 - 避免缓存过多: 只缓存必要的资源。预缓存关键资源,动态缓存按需添加。
- 版本控制: 每次更新静态资源或 Service Worker 逻辑时,务必更新缓存名称 (
-
调试:
- Chrome DevTools -> Application -> Service Workers: 查看注册状态、调试代码、强制更新、模拟离线状态、检查 CacheStorage。
- Chrome DevTools -> Network: 查看请求是否被 Service Worker 拦截(
Size
列显示(ServiceWorker)
),状态码为200 (from ServiceWorker)
。
-
用户体验:
- 离线页面: 在
fetch
事件中,如果网络和缓存都失败,可以返回一个预缓存的离线页面 (offline.html
)。 - 更新提示: 监听
registration
对象的updatefound
事件和installing
worker 的状态变化,通知用户有更新可用,并引导刷新页面以激活新 Service Worker。
- 离线页面: 在
总结:
实现 Service Worker 缓存的核心步骤是:注册 -> 安装时预缓存关键资源 -> 激活时清理旧缓存 -> 在 fetch
事件中实现缓存策略拦截请求并返回响应 。它提供了无与伦比的灵活性和控制力,是构建快速、可靠、离线可用 的现代 Web 应用(PWA)的基石。在面试中,清晰地阐述其核心概念、生命周期、事件驱动模型(特别是 install
, activate
, fetch
)以及如何实现不同的缓存策略,能充分展示你对高级浏览器缓存机制的理解。
6、浏览器是如何决定哪些资源缓存到本地哪些资源缓存到内存呢
浏览器决定将资源缓存到磁盘(Disk Cache / HTTP Cache) 还是内存(Memory Cache) 是一个复杂的动态决策过程,涉及多种因素。开发者无法直接控制,但理解其逻辑有助于优化缓存策略。以下是关键决策因素:
一、核心决策因素
1. 资源类型与大小
存储位置 | 典型资源类型 | 原因 |
---|---|---|
内存缓存 | - 小体积资源(JS、CSS、小图标) - 当前页面高频访问的资源(如首屏图片、核心JS) | 内存读取速度极快,适合快速响应当前页面的重复请求。 内存空间有限,优先存高频小资源。 |
磁盘缓存 | - 大体积资源(大图、视频、字体文件) - 低频访问的静态资源 - 强缓存策略明确的资源(max-age 较长) |
磁盘空间大(GB级),适合存大文件。 持久化存储,可跨会话使用。 |
2. 缓存策略(HTTP头部)
-
Cache-Control
优先级最高:- 若资源标记为
no-store
→ 不缓存。 - 若为
no-cache
→ 缓存但每次需验证(可能存内存或磁盘)。 - 若为
max-age=3600
→ 按策略缓存(磁盘优先,大资源存磁盘)。
- 若资源标记为
-
启发式缓存 :无明确过期时间时,浏览器根据
Last-Modified
时间推算有效期(如(Date - Last-Modified) * 0.1
),通常存磁盘。
3. 访问频率与时效性
行为 | 存储倾向 | 案例 |
---|---|---|
同一页面内多次请求 | 内存缓存 | 首页加载后,脚本二次请求(如异步加载模块)直接从内存读取。 |
跨页面/会话重复请求 | 磁盘缓存 | 站点LOGO图片在多页面间共享,从磁盘读取。 |
短时效资源 | 内存缓存 | 实时性较高的API响应(如股票数据),短暂存内存。 |
4. 浏览器内部算法
-
LRU(Least Recently Used)算法:
- 内存缓存 :空间紧张时优先淘汰最久未使用的小资源。
- 磁盘缓存:自动清理过期资源,按访问频率保留常用资源。
-
分区限制:
- 内存缓存:单进程/Tab独立(Chrome),关闭Tab即释放。
- 磁盘缓存:全局共享,受操作系统磁盘空间限制。
二、开发者视角的优化建议
1. 利用HTTP头部引导缓存
ini
# 引导存磁盘(大文件/长期缓存)
Cache-Control: public, max-age=31536000, immutable
# 引导存内存(小文件/高频资源)
Cache-Control: max-age=600, must-revalidate
注:浏览器最终决策权仍在其内部算法。
2. 资源分割策略
- 高频小资源 :合并为单文件(如Webpack打包的
vendor.js
)→ 易被内存缓存。 - 低频大资源:单独拆分(如视频、字体)→ 避免挤占内存缓存。
3. 避免破坏缓存的行为
- 随机查询参数 :
image.png?v=123
会被视为新资源,导致缓存失效。 - 动态内容无缓存头 :API响应未设
Cache-Control
→ 不缓存或短时内存缓存。
三、缓存位置验证(开发者工具)
在 Chrome DevTools 中查看:
-
Network面板 → 查看资源请求的
Size
列:(memory cache)
→ 内存缓存(disk cache)
→ 磁盘缓存
-
Application面板 →
Cache Storage
:查看Service Worker缓存。Cache
:查看HTTP磁盘缓存内容(需手动刷新)。
四、特殊场景处理
场景 | 缓存行为 |
---|---|
无痕模式 | 内存缓存可用,磁盘缓存禁用(关闭标签后清除所有缓存)。 |
页面刷新(硬刷新) | 跳过内存缓存,强制检查磁盘缓存和网络(Cache-Control: max-age 仍有效)。 |
Service Worker拦截 | 优先级最高,可覆盖内存/磁盘缓存(如fetch 事件返回自定义响应)。 |
总结:浏览器缓存决策逻辑
核心原则 : 浏览器以性能最优为目标,综合资源类型、大小、访问频率、HTTP策略动态分配存储位置。开发者应通过合理的缓存头部和资源优化策略,间接引导浏览器缓存行为。