详解浏览器缓存

缓存可以说是性能优化中简单高效的一种优化方式了。一个优秀的缓存策略可以缩短网页请求资源的距离,减少延迟,并且由于缓存文件可以重复利用,还可以减少带宽,降低网络负荷。

对于一个数据请求来说,可以分为发起网络请求、后端处理、浏览器响应三个步骤。浏览器缓存可以帮助我们在第一和第三步骤中优化性能。比如说直接使用缓存而不发起请求,或者发起了请求但后端存储的数据和前端一致,那么就没有必要再将数据回传回来,这样就减少了响应数据。

缓存位置

缓存从缓存位置上来说分为四种,并且各自有优先级,当依次查找缓存且都没有命中的时候,才会去请求网络。

  • Service Worker
  • Memory Cache
  • Disk Cache
  • Push Cache

Service Worker

Service Worker 是运行在浏览器背后的独立线程,一般可以用来实现缓存功能。使用 Service Worker 的话,传输协议必须为 HTTPS。因为 Service Worker 中涉及到请求拦截,所以必须使用 HTTPS 协议来保障安全。Service Worker 的缓存与浏览器其他内建的缓存机制不同,它可以让我们自由控制缓存哪些文件、如何匹配缓存、如何读取缓存,并且缓存是持续性的

Service Worker 实现缓存功能一般分为三个步骤:首先需要先注册 Service Worker,然后监听到 install 事件以后就可以缓存需要的文件,那么在下次用户访问的时候就可以通过拦截请求的方式查询是否存在缓存,存在缓存的话就可以直接读取缓存文件,否则就去请求数据。

当 Service Worker 没有命中缓存的时候,我们需要去调用 fetch 函数获取数据。也就是说,如果我们没有在 Service Worker 命中缓存的话,会根据缓存查找优先级去查找数据。但是不管我们是从 Memory Cache 中还是从网络请求中获取的数据,浏览器都会显示我们是从 Service Worker 中获取的内容。

关于 Service Worker 还有许多内容,以后单独解析。

借助 Service Worker 和 cacheStorage 缓存及离线开发

浏览器缓存、CacheStorage、Web Worker 与 Service Worker

Memory Cache 和 Disk Cache

Memory Cache 指的是内存缓存,从效率上讲它是最快的。但是从存活时间来讲又是最短的,一旦我们关闭 Tab 页面,内存中的缓存也就被释放了

Disk Cache 就是存储在磁盘中的缓存,从存取效率上讲是比内存缓存慢的,但是他的优势在于存储容量和存储时长。

好,现在问题来了,既然两者各有优劣,那浏览器如何决定将资源放进内存还是硬盘呢?主要策略如下:

  • 比较大的 JS、CSS 文件会直接被丢进磁盘,反之丢进内存
  • 内存使用率比较高的时候,文件优先进入磁盘

一些具体的表现可以看知乎上的这个回答

浏览器是根据什么决定「from disk cache」与「from memory cache」?

memoryCache 和 diskCache 流程详解

Push Cache

push cache 是HTTP/2的内容,它是浏览器缓存的最后一道防线,它只在会话(Session)中存在,一旦会话结束就被释放,并且缓存时间也很短暂,在 Chrome 浏览器中只有 5 分钟左右,同时它也并非严格执行 HTTP 头中的缓存指令。

HTTP/2 push is tougher than I thought

缓存策略

浏览器缓存策略分为两种:强缓存协商缓存,并且缓存策略都是通过设置 HTTP Header 来实现的。

强缓存

浏览器首先使用的是强缓存

使用强缓存的时候不会发送HTTP请求,直接从缓存中读取资源,返回状态码为 200,size 显示为 from disk cache 或 from memory cache。

强缓存可以通过设置两种 HTTP Header 实现:ExpiresCache-Control

Expires(HTTP/1.0)

Expires即过期时间,存在于服务端返回的响应头中,告诉浏览器在这个过期时间之前可以直接从缓存里面获取数据,无需再次请求。比如下面这样:

Expires: Wed, 22 Nov 2019 08:41:00 GMT

表示资源在2019年11月22号8点41分过期,过期了就得向服务端发请求。

这个方式看上去没什么问题,合情合理,但其实潜藏了一个坑,那就是服务器的时间和浏览器的时间可能并不一致,那服务器返回的这个过期时间可能就是不准确的。因此这种方式很快在后来的 HTTP1.1 版本中被抛弃了。

Cache-Control(HTTP/1.1)

在 HTTP/1.1 中,Cache-Control是最重要的规则,主要用于控制网页缓存。

Cache-Control 可以在请求头或者响应头中设置,并且可以组合使用多种指令:

cache-control: public, max-age=31536000

强缓存的机制可以用下面这张图来概括

协商缓存

强缓存失效之后,就进入了协商缓存阶段

浏览器在请求头中携带缓存标识,服务器根据缓存标识决定是否使用缓存,协商缓存生效则返回 304 和 Not Modified,失效则返回 200 和请求结果

协商缓存可以可以通过设置两种 HTTP Header 实现:Last-ModifiedETag

Last-Modified 和 If-Modified-Since

浏览器第一次访问资源时,response header 携带 Last-Modified(资源在服务器上的最后修改时间,最小单位 s),浏览器接收后缓存,下一次请求这个资源时 request header 携带 If-Modified-Since,如果修改时间没有变化,就使用缓存,返回 304,否则返回新资源和 200

Last-Modified: Tue, 30 Mar 2021 03:30:52 GMT

Last-Modified 存在一些弊端

  • 如果本地打开缓存文件,即使没有对文件进行修改,但还是会造成 Last-Modified 被修改,服务端不能命中缓存导致发送相同的资源
  • 因为 Last-Modified 只能以秒计时,如果在不可感知的时间内修改完成文件,那么服务端会认为资源还是命中了,不会返回正确的资源

ETag 和 If-None-Match

Etag 是服务器响应请求时,返回当前资源文件的一个唯一标识(由服务器生成),只要资源有变化,Etag 就会重新生成。浏览器在下一次加载资源向服务器发送请求时,会将上一次返回的 Etag 值放到 request header 里的If-None-Match里,服务器只需要比较客户端传来的 If-None-Match 跟自己服务器上该资源的 ETag 是否一致,就能很好地判断资源相对客户端而言是否被修改过了。

如果服务器发现 ETag 匹配不上,那么会返回 200 和新的资源(当然也包括了新的 ETag)发给客户端;

如果 ETag 是一致的,则直接返回 304 知会客户端直接使用本地缓存即可。

缓存机制

强制缓存优先于协商缓存进行,若强制缓存(Expires 和 Cache-Control)生效则直接使用缓存,若不生效则进行协商缓存(Last-Modified / If-Modified-Since 和 Etag / If-None-Match),协商缓存由服务器决定是否使用缓存,若协商缓存失效,那么代表该请求的缓存失效,返回 200,重新返回资源和缓存标识,再存入浏览器缓存中;生效则返回 304,继续使用缓存。具体流程图如下:

看到这里,不知道你是否存在这样一个疑问:如果什么缓存策略都没设置,那么浏览器会怎么处理?

对于这种情况,浏览器会采用一个启发式的算法,通常会取响应头中的 Date 减去 Last-Modified 值的 10% 作为缓存时间。

实际场景应用缓存策略

不使用缓存资源

1. meta 缓存头设置为禁止缓存

在 html 的 head 标签中加入下面内容,就可以禁止浏览器读取缓存

<meta http-equiv="Cache-Control" content="no-cache, no-store, must-revalidate" />
<meta http-equiv="Pragma" content="no-cache" />
<meta http-equiv="Expires" content="0" />

Cache-Control作用于HTTP1.1 Pragma作用于HTTP1.0 Expires作用于proxies

但这样浏览器在资源没修改的时候也不能加载缓存,十分影响体验

2. js、css 加上版本号

当请求 js、css 的时候,给他们最后加上版本号,浏览器发现版本高了,就自然而然不会读取低版本的缓存了 版本号并不需要改变文件名,只需要在调用 js、css 的时候在最末尾加上?v=1.0即可,比如

custom.css?v=1.0
main.js?v=2.0

当然版本号也可以自动添加随机数,不过这样就违背了版本号的初衷了,这样同样浏览器在资源没修改的时候也不能加载缓存,影响体验 随机版本号的添加方法,使用一个随机函数即可,当然,这样就只能通过 js 中写 js 的调用语句,比如

document.write(" <script src='test.js?v= " + Math.random() + " '></s " + " cript> ")

或者是

var js = document.createElement(" script ")
js.src = " test.js" + "?v=" + Math.random()
document.body.appendChild(js)

3. 添加 MD5

MD5 相当于一个文件的身份证号,每个文件的 MD5 都不一样,同一个文件只要经过修改,MD5 也不一样,所以我们可以通过 MD5 判断资源是否经过修改。当然这不可能让我们自己一个一个判断添加,肯定是服务器的事,我们这里不多说

频繁变动的资源

Cache-Control: no-cache

对于频繁变动的资源,首先需要使用Cache-Control: no-cache 使浏览器每次都请求服务器,然后配合 ETag 或者 Last-Modified 来验证资源是否有效。这样的做法虽然不能节省请求数量,但是能显著减少响应数据大小。

不常变化的资源

Cache-Control: max-age=31536000

通常在处理这类资源时,给它们的 Cache-Control 配置一个很大的 max-age=31536000 (一年),这样浏览器之后请求相同的 URL 会命中强制缓存。而为了解决更新的问题,就需要在文件名(或者路径)中添加 hash, 版本号等动态字符,之后更改动态字符,从而达到更改引用 URL 的目的,让之前的强制缓存失效 (其实并未立即失效,只是不再使用了而已)。
在线提供的类库 (如 jquery-3.3.1.min.js, lodash.min.js 等) 均采用这个模式。

用户行为对浏览器缓存的影响

所谓用户行为对浏览器缓存的影响,指的就是用户在浏览器如何操作时,会触发怎样的缓存策略。主要有 3 种:

  • 打开网页,地址栏输入地址: 查找 disk cache 中是否有匹配。如有则使用;如没有则发送网络请求。
  • 普通刷新 (F5):因为 TAB 并没有关闭,因此 memory cache 是可用的,会被优先使用(如果匹配的话)。其次才是 disk cache。
  • 强制刷新 (Ctrl + F5):浏览器不使用缓存,因此发送的请求头部均带有 Cache-control: no-cache(为了兼容,还带了 Pragma: no-cache),服务器直接返回 200 和最新内容。

ref

浏览器缓存机制

谈谈前端缓存

如何解决静态资源的缓存问题

详解浏览器缓存 前端开发必会相关推荐

  1. 深入详解数据库事务(开发必用)

    一.事务的概念: 一组逻辑操作单元,时数据从一个状态转换到另一个状态. 二.事务处理的原则: 保证所有的事务都被当做一个操作单元来执行,即使出现了故障,也不能改变这种处置原则.要么与事务相关的数据全部 ...

  2. java cache-control_详解浏览器Cache-Control缓存策略

    原来用的是 Expires 策略,浏览器可以直接从浏览器缓存读取数据,而无需再次请求,它的值对应一个 GMT,来告诉浏览器资源缓存过期时间,如果还没过该时间点则不发请求. 例如下面的例子,这是京东的首 ...

  3. 10年后端开发程序员详解数据库缓存方案到底有多少名堂。丨Linux服务器开发丨后端开发丨中间件丨web服务器丨数据库缓存

    数据库缓存方案到底有多少花样,一节课带你缕清 1. 读写分离方案 2. 若干个缓存解决方案 3. 缓存故障如何解决 视频讲解如下,点击观看: 10年后端开发程序员详解数据库缓存方案到底有多少名堂.丨L ...

  4. 详解微信小程序开发(项目从零开始)

    关注公众号 风色年代(itfantasycc) 280G前端&小程序资料随便拿! 详解微信小程序开发(项目从零开始) 一.序 微信小程序,估计大家都不陌生,现在应用场景特别多.今天就系统的介绍 ...

  5. html 5效果不显示,详解如何解决H5开发使用wx.hideMenuItems无效果不生效

    情况:引入SDK 的签名不报错与调试工具生成的结果也是一模一样,但是使用hideMenuItems没有小效果,不会报错. 解决方式:把要执行的wx.hideMenuItems()放到wx.ready这 ...

  6. 网站前端开发必会基础知识有哪些?

    自己工作做得好好的,怎么非要去搞前端?" 很多人离职的时候,可能印象最深的就是爸妈每天说的这句话.起因很简单,就是自己辞了爸妈眼中的"铁饭碗". 他也是如此,毅然辞去了一 ...

  7. 完全手册-MATLAB使用详解:基础、开发及工程应用

    [书名]完全手册-MATLAB使用详解:基础.开发及工程应用 [作者]董霖 编著 [ISBN]978-7-121-07397-7 [出版社]电子工业出版社 [出版日期]2009年1月 [内容简介] M ...

  8. java基础(十三)-----详解内部类——Java高级开发必须懂的

    java基础(十三)-----详解内部类--Java高级开发必须懂的 目录 为什么要使用内部类 内部类基础 静态内部类 成员内部类 成员内部类的对象创建 继承成员内部类 局部内部类 推荐博客 匿名内部 ...

  9. 无论小白还是大佬,前端开发必不能少了Ta

    浏览器内核 浏览器是前端开发必不可少的东西,没有了浏览器,前端再好的代码也都是白给,而浏览器内核也是浏览器必不可少的东西.但是大家有没有真正的深入了解过浏览器呢? 内核,即:引擎 排版引擎(layou ...

最新文章

  1. jQuery Pagination分页插件--无刷新
  2. 2路由策略_route-map(执行路由策略)
  3. - 动规讲解基础讲解八——正整数分组
  4. 团队—贪吃蛇—需求分析
  5. java回合制武侠手游_‎App Store 上的“群侠传-怀旧开放武侠RPG回合制手游”
  6. 安装完最小化 RHEL/CentOS 7 后需要做的 30 件事情(一)
  7. java audiostream 用不了_AudioInputStream不起作用
  8. python 双冒号切片_Numpy 学习笔记
  9. 大数据面试都问些什么?
  10. 互联网晚报 | 7月10日 星期天 | 快手官宣:7月18日周杰伦独家直播;​400亿额度,秒光!7月总票房破10亿...
  11. EIA/TIA布线标准(568A、568B)
  12. 手机电源键关不了屏幕_手机关机关不了,屏幕也划不了,怎么办
  13. websocket 服务器外网访问
  14. 云南大学计算机学院导师信息,云南大学软件学院研究生导师介绍:姚绍文(教授,博士生导师)...
  15. 非上市公司股权激励方案(珍藏版)
  16. linux socket 编程
  17. 图片和文字对齐的方法
  18. DataGrip 太好使了
  19. 大数据平台资源治理经验总结
  20. Linux 下的截屏并编辑的工具-flamshot安装及使用

热门文章

  1. 亚信科技笔试java
  2. 手机二维码扫码登录(Java源码及思路)
  3. 【STL】push 和 emplace区别
  4. php试卷分析,对试卷分析的必要性与试卷分析的技巧
  5. FastAPI框架,数据库迁移生成及增删改查
  6. 大数据—Hive(一)_ 基本概念
  7. 如何用r语言分析数据
  8. 淘宝API接口,item_cat_get-获得淘宝商品类目
  9. Linux命令--mkdir命令:创建目录(文件夹)
  10. 手机分辨率PPI和DPI的区别