【前言】
## 数据的缓存
数据 既可以在 浏览器(客户端)本地存储,也可以在 服务器端存储,
- 浏览器端(客户端) 可以保存一些数据,需要的时候直接从本地获取。
- 服务器端 可以保存所有用户的数据,但需要的时候浏览器要向服务器请求数据。
## Web缓存从微观上可以分为以下几类:
- 浏览器缓存 (每个浏览器都实现了HTTP缓存,当我们通过浏览器使用HTTP协议与服务器交互时,浏览器就会根据一套与服务器约定好的规则进行缓存工作)
- 数据库缓存 (当web应用关系复杂,数据表剧增时,可以将查询后的数据进行缓存,下次再查询时,就直接从内存缓存中获取,从而提高响应速度)
- 服务端缓存 (此类缓存跟浏览器缓存类似,但其面向的群众更广,规模更大。它不只为一个用户服务,一般为大量用户服务,同一个副本会被重用多次,因此在减少响应时间和带宽使用方面很有效)
- CDN缓存 (属于服务端缓存,当我们发送一个web请求时,CDN会帮我们计算去哪儿得到这些数据的路径短且快。这是网址管理员部署的,所以他们也可以将大家经常访问的内容放在CDN里,从而加快响应)
- 代理服务器缓存 (属于服务端缓存,此类缓存跟浏览器缓存类似,但其面向群众更广,规模更大。它不只为一个用户服务,一般为大量用户服务,同一个副本会被重用多次,因此在减少响应时间和带宽使用方面很有效)
## 注意区分 浏览器缓存 的概念!!!
浏览器缓存 包含很多内容: Disk Cache(Http缓存)、Memory Cache、ServiceWorker、Push Cache 等,是 浏览器根据用户配置 自动 拷贝文件,将文件副本存储在电脑中作为 缓存以便下次打开页面使用。
(1)浏览器缓存的概述
## 浏览器缓存的工作是什么?
浏览器缓存就是把一个已经请求过的 web资源(如 html页面,图片,JS,数据)拷贝 一份放在 浏览器 中。
缓存会根据进来的请求保存输入内容的副本,当下一个请求到来的时候,如果是 相同的URL,浏览器会根据缓存机制决定是直接使用副本响应访问请求还是向源服务器再次发起请求。
当我们在浏览器中点击 前进 和 后退 按钮时,利用的便是浏览器缓存机制。
##浏览器缓存在什么时候起作用?
对于一个数据请求来说,可以分为 发起网络请求、后端处理、浏览器响应 三个步骤。
浏览器缓存可以帮助我们在 发起网络请求 和 浏览器响应 中 优化性能。
① 不发起请求,直接使用缓存;
② 发起请求,但后端存储的数据和前端一致,那么就没有必要再将数据回传回来;
这样就 减少了 响应数据。
## 使用浏览器缓存的原因?
- 减少网络带宽消耗 当web缓存副本被使用时,只会产生极小的网络流量,可以有效降低运营成本。
- 降低服务器压力 给网络资源设定有效期后,用户可以重复使用本地缓存,减少对源服务器的请求,从而降低对服务器的压力;同时搜索引擎的爬虫机器人也能根据 过期机制 降低爬取的频率,也能有效降低服务器压力。
- 减少网络延迟 缓存的使用可以明显加快页面打开速度,达到更好的用户体验。
## 浏览器的缓存过程如下:
【1】开始加载,域名解析,DNS缓存;
(DNS缓存:它包含了域名和端口可以指定唯一的IP地址,然后建立连接进行通信,而域名查找IP地址的过程就是dns解析:www.dnscache.com (域名) -- DNS解析 --> 11.222.33.444 (IP地址)
这个过程会对网络请求带来一定的损耗,所以浏览器在第一次获取到IP地址后,会将其缓存起来)
【2】本地缓存(Memory Cache);
(memory cache 是浏览器为了加快读取缓存速度而进行的自身的优化行为,不受开发者控制,也不受 HTTP 协议头的约束,算是一个黑盒)
【3】Http缓存(也叫Disk Cache,分为 强缓存 和 协商缓存);
【4】服务端缓存(CDN缓存,解释见上);
(2)浏览器缓存过程
实际上,HTTP 协议头 的那些字段,都属于 Disk Cache 的范畴,是几个缓存位置的其中之一。我们可以在 Chrome 的开发者工具中的 “Network —> Size” 一列 看到一个请求最终的处理方式:如果是大小 (多少 K, 多少 M 等) 就表示是 网络请求,否则会列出 from memory cache,from disk cache 和 from ServiceWorker。
Disk Cache(Http缓存)根据 是否需要向服务器 重新发起HTTP请求 将 浏览器缓存 分为 两个部分(强缓存、协商缓存)
### Http缓存主要是 HTTP 协议定义的缓存机制
HTML meta 标签,例如
<META HTTP-EQUIV="Pragma" CONTENT="no-store">
含义是让浏览器不缓存当前页面。但是代理服务器不解析 HTML 内容,一般 应用广泛的是用 HTTP 头信息控制缓存。
### 在具体了解 HTTP 缓存之前先来明确几个术语:
- 缓存命中率:从缓存中得到数据的请求数与所有请求数的比率 —— 理想状态是越高越好。
- 过期内容:超过设置的有效时间,被标记为“陈旧”的内容 —— 通常过期内容不能用于回复客户端的请求,必须重新向源服务器请求新的内容或者验证缓存的内容是否仍然准备。
- 验证:验证缓存中的过期内容是否仍然有效,验证通过的话刷新过期时间。
- 失效:失效就是把内容从缓存中移除 —— 当内容发生改变时就必须移除失效的内容。
① 强缓存
不会向服务器发送请求,直接从缓存中读取资源,在控制台的Network选项中可以看到该请求返回200的状态码,并且Size显示from disk cache 或 from memory cache。
强缓存我们会把资源放到 Memory Cache 和 Disk Cache 中。
## 如何设置 HTTP Header 实现 强缓存?
强缓存可以通过设置两种 HTTP Header 实现 —— Expires 和 Cache-Control:Expires(HTTP/1 的产物,受限于计算机本地时间)
缓存过期时间,用来 指定资源到期的时间,是服务器端的具体的时间点。
也就是说,Expires=max-age + 请求时间,需要和 Last-modified(服务器响应请求时,返回该资源文件在服务器最后被修改的时间) 结合使用。
Expires 是Web服务器 响应消息头字段,在响应http请求时告诉浏览器 在过期时间前 浏览器可以直接从 浏览器缓存 取数据,而无需再次请求。
Cache-Control(在HTTP/1.1中,Cache-Control是最重要的规则,主要用于控制网页缓存)
比如:当 Cache-Control:max-age=300 时,则代表在这个请求成功返回后的 5分钟内(5*60=300)再次加载资源,就会命中强缓存。
Cache-Control 可以在请求头或者响应头中设置,并且可以组合使用多种指令:
(我们可以将多个指令配合起来一起使用,达到多个目的。比如说我们希望资源能被缓存下来,并且是客户端和代理服务器都能缓存,还能设置缓存失效时间等等 )
② 协商缓存
强缓存 失效后,浏览器 携带缓存标识 向服务器 发起请求,由服务器 根据 缓存标识 决定是否使用缓存 的过程。
## 如何设置 HTTP Header 实现 协商缓存?
<1> Etag / If-None-Match
Etag 是 服务器响应请求时,返回 当前资源文件 的一个唯一标识(由服务器生成,一旦资源有更新就会生成新的Etag值)。
If-None-Match 是客户端再次发起该请求时,携带 上次请求返回的唯一标识 Etag值,通过此字段值告诉服务器该资源上次请求返回的唯一标识值。
服务器收到该请求后,发现该请求头中含有 If-None-Match,则会根据 If-None-Match 的值 与 Etag 的值 做对比,
- If-None-Match 与Etag的值一致,则 代表 服务端的资源在上一次请求到这次请求的期间未发生更新,可 继续使用缓存文件,无须返回资源,状态码为304;
- f-None-Match 与Etag的值不一致,则 代表 资源发生过更新,须重新返回资源文件,状态码为200;
【插曲】Etag / If-None-Match 优先级 高于 Last-Modified / If-Modified-Since
二者同时存在时,则只有Etag / If-None-Match生效。
<2> Last-Modified / If-Modified-Since
Last-Modified 是服务器响应请求时,返回该 资源文件 在服务器 最后被 修改 的时间。(由服务器生成,跟随 服务器 返回响应请求头 返回到客户端)
If-Modified-Since 是客户端再次发起该请求时,浏览器自动携带上次请求返回的 Last-Modified 值,通过此字段值告诉服务器该资源上次请求返回的 最后被 修改 的时间。(由开发者手动设置)
服务器收到该请求,发现请求头含有 If-Modified-Since 字段,则会根据 If-Modified-Since 的值 与 携带的 Last-Modified 的值 做对比,
- Last-Modified 小于 If-Modified-Since 的字段值,则 代表 服务端的资源在上一次请求到这次请求的期间未发生更新,可 继续使用缓存文件,无须返回资源,则状态码为304;
- Last-Modified 大于 If-Modified-Since 的字段值,则 代表 资源发生过更新,须 重新返回资源,则状态码为200;
## 协商缓存 结果 主要有以下两种情况 ————
<1> 协商缓存生效
返回304和Not Modified,继续使用缓存:
<2> 协商缓存失效
代表该请求的缓存失效,返回200,重新返回 资源&缓存标识 并再存入浏览器缓存中:
## 强缓存 与 协商缓存 的区别?
- 强缓存 不发请求到服务器,所以有时候资源更新了浏览器还不知道,但是协商缓存会发请求到服务器,所以资源是否更新,服务器肯定知道。
- 大部分 web服务器 都 默认 开启协商缓存。
## 刷新 对于 强缓存 和 协商缓存 的影响?
- 当 ctrl+f5 强制刷新 网页时,直接 从服务器加载,跳过强缓存和协商缓存。
- 浏览器地址栏中写入URL,先检查 强缓存,若失效则检查 协商缓存。(最快)
- 当 f5刷新 网页时,跳过强缓存,但是会检查 协商缓存。
## 缓存方案:
目前大多数项目使用的缓存方案:
- 强缓存 :css、js、图片文件,文件名带上 hash;
- 协商缓存 : html 文件;
## H5页面 的 缓存机制:
优先级:appcache(属于html5的离线存储)> 强缓存 > 协商缓存。
若强缓存生效则直接使用缓存,若不生效则进行协商缓存( Last-Modified / If-Modified-Since 和 Etag / If-None-Match)
### appcache (Application Cache) 简介
该特性已经从 Web 标准中删除,虽然一些浏览器目前仍然支持它,但也许会在未来的某个时间停止支持,请尽量不要使用该特性。
从可用性与易用性来说,Application Cache是最好是做 静态公共资源 的缓存,尽量不去缓存业务资源。
使用appcache的话,需要在index.html下,也就是项目目录下,新建一个类似于cache.manifest的manifest文件并在index.html下的html标签内新增manifest属性。
### appcache 时效性
一旦应用被缓存,它就会保持缓存直到发生下列情况:
● 用户清空浏览器缓存
● manifest 文件被修改(参阅下面的提示)
● 由程序来更新应用缓存
### appcache 使用案例
<!DOCTYPE HTML>
<html manifest = "cache.manifest">
...
</html>
CACHE MANIFEST
# 2022-03-02 v1.0.0
#在cache.manifest下应当写明版本号,并且对本文件不做缓存处理。
#以 "#" 开头的是注释行,但也可满足其他用途。
#更新注释行中的 日期和版本号 是一种使浏览器 重新缓存文件 的办法。
CACHE:
#需要被缓存的文件
js/app.js
css/style.css
NETWORK:
#不需要被缓存的文件
resourse/logo.png
FALLBACK:
#表示如果访问第一个资源失败,那么就使用第二个资源来替换他,比如上面这个文件表示的就是如果访问根目录下任何一个资源失败了,那么就去访问offline.html
/ /offline.html
(3)浏览器缓存的 位置(来源)和 查找优先级
从缓存位置(来源)和 缓存查找优先级说,浏览器的缓存可以分为四类(Service Worker、Memory Cache、Disk Cache、Push Cache)
查找浏览器缓存时会按顺序查找: Service Worker —> Memory Cache —> Disk Cache —> Push Cache,当依次查找缓存且都没有命中的时候,才会去请求网络。
① Service Worker
(数据获取到的位置可以是内存、硬盘、服务端数据,主要根据开发者的设置来决定)
(在 web worker 的基础上增加了 离线缓存 的能力,本质上充当 服务器 与 浏览器 之间的 代理服务器)
## 简介
Service Worker 是运行在浏览器背后的独立线程,一般可用来实现缓存功能,传输协议必须为 HTTPS —— Service Worker 中涉及到请求拦截,所以必须使用 HTTPS 协议来保障安全。
Service Worker 的缓存与浏览器 其他 内建的缓存机制 不同,它可以让我们 自由控制 缓存哪些文件、如何匹配缓存、如何读取缓存,并且缓存是 持续性的。
它 采用 JavaScript 控制 关联的页面或者网站,拦截并修改 访问和资源请求,细粒度地 缓存资源。你可以完全控制应用在特定情形(最常见的情形是网络不可用)下的表现。
Service Worker 能够操作的缓存是有别于浏览器内部的 memory cache 或者 disk cache 的。
我们可以从 Chrome 的 F12 中,“Application —>Cache Storage” 找到这个单独的 “小金库”。
如果 Service Worker 没能命中缓存,一般情况会使用 fetch() 方法 继续获取资源,这时候,浏览器就去 memory cache 或者 disk cache 进行下一次找缓存的工作了。
【注意】经过 Service Worker 的 fetch() 方法获取的资源,无论最终是在哪儿取到的资源,甚至实际走了网络请求,也会标注为 from ServiceWorker
## Service Worker 的特点
- 创建有效的离线体验(将一些不常更新的内容缓存在浏览器,提高访问体验);
- 由 事件 驱动 的,具有生命周期(下载 —> 安装 —> 激活);
- 可以访问 cache(包含:Memory Cache、Disk Cache、Push Cache) 和 indexedDB;
- 支持推送;
- 可以让开发者自己控制管理缓存的内容以及版本;
- 它设计为完全异步;
- Service worker运行在worker上下文,因此它不能访问DOM;
## Service Worker 实现缓存功能一般分为三个步骤:
首先需要先 注册 Service Worker,
然后监听到 install 事件 以后就可以缓存需要的文件,那么在下次用户访问的时候就可以通过 拦截请求的方式 查询 是否存在缓存,
存在 缓存的话就 可以 直接读取缓存 文件,否则 就去服务端请求数据。
( 也就是说,如果我们没有在 Service Worker 命中缓存的话,会根据 缓存查找优先级 去查找缓存数据。但是不管我们是从 Cache 中还是从网络请求中获取的数据,浏览器都会显示我们是从 Service Worker 中获取的内容。)
## 一个简单的例子:
//【1】注册Service worker 在你的index.html加入以下内容
/* 判断当前浏览器是否支持serviceWorker */
if ('serviceWorker' in navigator) {
/* 当页面加载完成就创建一个serviceWorker */
window.addEventListener('load', function () {
/* 创建并指定对应的执行内容 */
/* scope 参数是可选的,可以用来指定你想让 service worker 控制的内容的子目录。 在这个例子里,我们指定了 '/',表示 根网域下的所有内容。这也是默认值。 */
navigator.serviceWorker.register('./serviceWorker.js', {scope: './'}) ---!!! 注册 !!!---///
.then(function (registration) {
console.log('ServiceWorker registration successful with scope: ', registration.scope);
})
.catch(function (err) {
console.log('ServiceWorker registration failed: ', err);
});
});
}
//【2】安装worker:在我们指定的处理程序serviceWorker.js中书写对应的安装及拦截逻辑
/* 监听安装事件,install 事件一般是被用来设置你的浏览器的离线缓存逻辑 */
this.addEventListener('install', function (event) {
/* 通过这个方法可以防止缓存未完成,就关闭serviceWorker */
event.waitUntil(
/* 创建一个名叫V1的缓存版本 */
caches.open('v1').then(function (cache) {
/* 指定要缓存的内容,地址为相对于跟域名的访问路径 */
return cache.addAll([
'./index.html'
]);
})
);
});
/* 注册fetch事件,拦截全站的请求:通过 拦截请求的方式 查询 是否存在缓存 */
this.addEventListener('fetch', function(event) {
event.respondWith(
// magic goes here
/* 在缓存中匹配对应请求资源直接返回 */
caches.match(event.request)
);
});
② Memory Cache
(数据获取到的位置是电脑内存)
内存缓存,主要包含当前中页面中已抓取到的资源,如 样式文件、脚本文件、图片文件等,一旦我们关闭 Tab 页面,内存缓存就被释放。
读取快,容量小,时效短 。
当我们访问过页面以后,再次刷新页面,可以发现很多数据都来自于内存缓存。
Memory Cache 是 浏览器 为了加快读取缓存速度而进行的 自身优化行为,不受开发者控制,也不受 HTTP 协议头的约束,算是一个黑盒。
(浏览器 主动地 根据资源使用情况,从 Disk 中选择经常被使用的 资源,将其 引用 保存到 Memory 中,以提供给自身的后续使用,从而达到加快缓存读取速度的目的)
③ Disk Cache(数据获取到的位置可以是电脑硬盘)
硬盘缓存,容量大,时效长,读取慢 。
在所有浏览器缓存中,绝大部分的缓存 都来自 Disk Cache。
它会根据 HTTP Herder 中的字段 判断 哪些资源 需要缓存,哪些资源 可以不请求直接使用,哪些资源 已经过期需要重新请求。
并且即使在 跨站点 的情况下,相同地址的资源一旦被硬盘缓存下来,就不会再次去请求数据。
## prefetch cache(预读取缓存)
link标签 上带了 prefetch,再次加载会出现。
prefetch 是预加载的一种方式,被标记为 prefetch 的资源,将会被 浏览器在 空闲时间 加载。
【插曲】 浏览器会把哪些文件丢进内存中?哪些丢进硬盘中?
关于这点,网上说法不一,不过以下观点比较靠得住:
- 对于大文件来说,大概率是存储在硬盘中的,反之存储在内存中;
- 当前系统内存使用率高的话,文件优先存储进硬盘;
④ Push Cache
推送缓存,是针对 HTTP/2 标准下的 推送资源 设定的,当以上三种缓存都没有命中时才会被使用。(应该是在服务端对已被请求的资源涉及的相关资源做推送缓存,通过这种预言客户端的下一步请求的模式,对客户端请求作出快速的响应,以提升页面性能。更多参考:HTTP/2 push is tougher than I thought - JakeArchibald.com)
它只在会话(Session)中存在,一旦会话结束就被释放,且 缓存时间短,比如在Chrome浏览器中只有5min左右,同时它也 并非严格执行HTTP头中的缓存指令。
## 推送缓存的特点:
- 几乎所有的资源都能被推送,并且能够被缓存(但是 Edge 和 Safari 浏览器支持相对比较差);
- 一旦 连接 被关闭,Push Cache 就被释放;
- 可以推送 no-cache 和 no-store 的资源;
- 多个页面 可以使用 同一个 HTTP/2 连接,也就可以使用 同一个Push Cache(这主要还是依赖浏览器的实现而定,出于对性能的考虑,有的浏览器会对相同域名但不同的 tab 标签使用同一个HTTP连接);
- Push Cache 中的缓存 只能被使用一次;
- 浏览器 可以拒绝接受 已经存在的 资源推送;
- 你可以给 其他域名 推送资源;
【小结】关于Service Worker、Http Cache、Push Cache之间的关系图解:
## <link rel= preload> 与 <link rel= prefetch> 的区别?
- preload(预加载) 是一个声明式 fetch,可以强制浏览器在不阻塞 document 的 onload 事件的情况下请求资源;
- prefetch(预读取) 告诉浏览器这个 资源将来可能需要,但是 什么时间 加载 这个资源是由浏览器来决定的;
Tips:preload 加载资源一般是当前页面需要的,prefetch 一般是其它页面有可能用到的资源。