缓存功能
缓存为了避免不必要的网络请求。
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天 |
ETag和Last-Modified(协商缓存)
通过设置 ETag 或 Last-Modified,可以让重新验证请求更加高效。它们最终会触发请求标头中的 If-Modified-Since 或 If-None-Match。
如果匹配,则服务器可以使用 304 Not Modified 进行响应。发送这种类型的响应时传输的数据很少,因此通常比返回实际请求资源的响应要快得多。
数据缓存
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
服务器可以从 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=Alice | Cookie 的名称和值 |
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 必须存在于单独的文件中。
if ("serviceWorker" in navigator) {
navigator.serviceWorker.register("/serviceworker.js");
}
拦截请求
self.addEventListener("fetch", event => {
console.log('WORKER: Fetching', event.request);
});
生命周期
Service Worker 的生命周期由多个步骤组成,每个步骤都会触发一个事件。
注册 Service Worker
浏览器下载 JavaScript 文件,安装 Service Worker,并触发事件
self.addEventListener("install", event => {
console.log("WORKER: install event in progress.");
});Service Worker 被激活,并触发事件
self.addEventListener("activate", event => {
console.log("WORKER: activate event in progress.");
});刷新页面或进入新页面,Service Worker 即可运行。如果想要不等待就运行 Service Worker,可以使用
self.skipWaiting()
self.addEventListener("install", event => {
self.skipWaiting();
// …
});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());
}
});