Skip to main content

缓存功能

缓存为了避免不必要的网络请求。

HTTP缓存

浏览器发出的所有 HTTP 请求首先会检查是否有满足请求的有效缓存响应,如果存在匹配,则从缓存中读取响应。

HTTP 缓存的行为由请求标头和响应标头一起控制。

所有浏览器都支持以下标头

标头描述
Cache-Control服务器可以通过返回该指令,指定浏览器对单个响应进行缓存的方式以及持续时间
Etag当浏览器发现过期的缓存响应时,它可以向服务器发送一个小令牌(通常是文件内容的 hash)来检查文件是否已更改。如果服务器返回了相同的令牌,说明文件没有改动,无需重新下载
Last-Modified用途与 ETag 相同,但它通过比较时间来确定资源是否已更改

版本化URL

假如你的服务器指示浏览器将 CSS 文件缓存一年 Cache-Control: max-age=3153600,但设计师刚刚进行了紧急更新,你需要立即推出此更新。

答:这个是做不到的,除非修改资源的 URL。通常来说,可以通过在文件名嵌入文件的版本号来实现这一点。例如,style.x234dff.css

Cache-Control(强缓存)

描述
no-cache每次使用缓存前都必须与服务器重新验证
no-store不允许缓存响应,必须在每次请求时完整获取
private, max-age=600响应可以被浏览器(但不是中间缓存,如 CDN)缓存最多10分钟
public, max-age=31536000响应可以由任何缓存存储1年
max-age=86400响应可以被浏览器和中间缓存缓存最多1天

Cache-Control

ETag和Last-Modified(协商缓存)

通过设置 ETag 或 Last-Modified,可以让重新验证请求更加高效。它们最终会触发请求标头中的 If-Modified-Since 或 If-None-Match。

如果匹配,则服务器可以使用 304 Not Modified 进行响应。发送这种类型的响应时传输的数据很少,因此通常比返回实际请求资源的响应要快得多。

数据缓存

Data-Cache

LocalStorage

本地存储,用于存储与用户相关的数据。

  • 存储容量5~10MB
  • 以字符串形式存储,需要手动进行序列化和反序列化
localStorage.setItem(“key”, “value”)
localStorage.getItem(“key”)
localStorage.removeItem(“key”)

SessionStorage

会话存储,用于存储一个会话的用户相关数据,关闭标签页或浏览器窗口时数据会被清除。

  • 存储容量5~10MB
  • 以字符串形式存储,需要手动进行序列化和反序列化
sessionStorage.setItem(“key”, “value”)
sessionStorage.getItem(“key”)
sessionStorage.removeItem(“key”)

服务器可以从 Cookie 获取数据以跟踪会话状态。

  • 存储容量4KB
  • 以键值对的形式存储
document.cookie = "username=Alice; expires=Thu, 18 Dec 2023 12:00:00 GMT; path=/; domain=.example.com; secure; HttpOnly; SameSite=Strict";

服务端通过响应标头的 Set-Cookie 进行设置。

标头描述
username=AliceCookie 的名称和值
expires=Thu, 18 Dec 2023 12:00:00 UTC指定 Cookie 的过期时间,即在2023年12月18日 12:00:00 UTC 之后将过期
path=/指定 Cookie 的路径,表示它适用于网站上的所有路径
domain=.example.com指定 Cookie 的域,表示它适用于所有以 .example.com 结尾的子域名
secure表示该 Cookie 只在通过 HTTPS 连接传输时才被发送
HttpOnly告诉浏览器该 Cookie 仅供服务器使用,JavaScript 无法访问
SameSite=Strict设置了 Cookie 的 SameSite 属性,以确保只在相同站点请求时发送 Cookie

Service Workers

Service Workers 服务工作进程是一种特殊的 Web 工作进程,能够进行 API 拦截、修改和响应网络请求。

Service Workers 可以用于缓存资源、离线页面支持。

Service-Workers

注册

Service Workers 必须存在于单独的文件中。

if ("serviceWorker" in navigator) {
navigator.serviceWorker.register("/serviceworker.js");
}

拦截请求

self.addEventListener("fetch", event => {
console.log('WORKER: Fetching', event.request);
});

生命周期

Service Worker 的生命周期由多个步骤组成,每个步骤都会触发一个事件。

  1. 注册 Service Worker

  2. 浏览器下载 JavaScript 文件,安装 Service Worker,并触发事件

    self.addEventListener("install", event => {
    console.log("WORKER: install event in progress.");
    });
  3. Service Worker 被激活,并触发事件

    self.addEventListener("activate", event => {
    console.log("WORKER: activate event in progress.");
    });
  4. 刷新页面或进入新页面,Service Worker 即可运行。如果想要不等待就运行 Service Worker,可以使用 self.skipWaiting()

    self.addEventListener("install", event => {
    self.skipWaiting();
    // …
    });
  5. Service Worker 正在运行,可以监听拦截 fetch 事件

缓存资源

// The name of the cache your app uses.
const CACHE_NAME = "my-app-cache";
// The list of static files your app needs to start.
const PRE_CACHED_RESOURCES = ["/", "styles.css", "app.js"];

// Listen to the `install` event.
self.addEventListener("install", event => {
async function preCacheResources() {
// Open the app's cache.
const cache = await caches.open(CACHE_NAME);
// Cache all static resources.
cache.addAll(PRE_CACHED_RESOURCES);
}

event.waitUntil(preCacheResources());
});

现在,可以使用 fetch 事件从从缓存中返回静态资源,而不用从网络中再次加载。

self.addEventListener("fetch", event => {
async function returnCachedResource() {
// Open the app's cache.
const cache = await caches.open(CACHE_NAME);
// Find the response that was pre-cached during the `install` event.
const cachedResponse = await cache.match(event.request.url);

if (cachedResponse) {
// Return the resource.
return cachedResponse;
} else {
// The resource wasn't found in the cache, so fetch it from the network.
const fetchResponse = await fetch(event.request.url);
// Put the response in cache.
cache.put(event.request.url, fetchResponse.clone());
// And return the response.
return fetchResponse.
}
}

event.respondWith(returnCachedResource());
});

离线页面支持

// The name of the cache your app uses.
const CACHE_NAME = "my-app-cache";
// The list of static files your app needs to start.
// Note the offline page in this list.
const PRE_CACHED_RESOURCES = ["/", "styles.css", "app.js", "/offline"];

// Listen to the `install` event.
self.addEventListener("install", event => {
async function preCacheResources() {
// Open the app's cache.
const cache = await caches.open(CACHE_NAME);
// Cache all static resources.
cache.addAll(PRE_CACHED_RESOURCES);
}

event.waitUntil(preCacheResources());
});

self.addEventListener("fetch", event => {
async function navigateOrDisplayOfflinePage() {
try {
// Try to load the page from the network.
const networkResponse = await fetch(event.request);
return networkResponse;
} catch (error) {
// The network call failed, the device is offline.
const cache = await caches.open(CACHE_NAME);
const cachedResponse = await cache.match("/offline");
return cachedResponse;
}
}

// Only call event.respondWith() if this is a navigation request
// for an HTML page.
if (event.request.mode === 'navigate') {
event.respondWith(navigateOrDisplayOfflinePage());
}
});

参考文章

https://web.dev/http-cache/#flowchart

https://medium.com/@lancelyao/browser-storage-local-storage-session-storage-cookie-indexeddb-and-websql-be6721ebe32a

https://learn.microsoft.com/en-us/microsoft-edge/progressive-web-apps-chromium/how-to/service-workers