JS基础系列之 —— 浏览器缓存

1、HTTP版本特性

HTTP (The HyperText Transfer Protocol,超文本传输协议) 是用于在 Web 上传输超媒体文件的底层 协议 ,最典型场景的是在浏览器和服务器之间传递数据,以供人们浏览。现行的 HTTP 标准的版本是 HTTP/2

1. HTTP0.9

http0.9是最古老的一个版本,1991年万维网协会和互联网工程任务组制定,组成极其简单。

  • 只允许客户端发送GET一种请求,并且也不支持请求头

  • 纯文本,由于没有协议头,所以HTTP0.9版本只支持纯文本内容。不过支持用HTML语言格式化,但是无法插入图片

  • 无状态性,每个事务独立处理,事务结束的时候就释放连接。HTTP协议的无状态特点在其第一个版本0.9中已经成型。

一次HTTP 0.9的传输首先要建立一个由客户端到Web服务器的TCP连接,由客户端发起一个请求,然后由Web服务器返回页面内容,然后连接会关闭。如果请求的页面不存在,也不会返回任何错误码。

2. HTTP1.0

1996年5月发布了1.0版本

  • 协议版本信息现在会随着每个请求发送(HTTP/1.0被追加到了GET行)

  • 引入了HTTP头的概念,无论是对于请求还是响应,允许传输元数据,使协议变得非常灵活,更具扩展性

  • 在新HTTP头的帮助下,具备了传输除纯文本HTML文件以外其他类型文档的能力(Content-Type头)

  • 通过expires、Last-Modified / If-Modified-Since来判断是否都去缓存内容

    GET /myimage.gif HTTP/1.0 User-Agent: NCSA_Mosaic/2.0 (Windows 3.1)

    200 OK Date: Tue, 15 Nov 1994 08:12:32 GMT Server: CERN/3.0 libwww/2.17 Content-Type: text/gif (这里是图片内容)

缺点:

  • 连接无法复用。HTTP1.0规定浏览器与服务器只保持短暂的连接,浏览器的每次请求都要和服务器建立一个TCP连接,服务器处理完成后立即断开TCP连接。

3. HTTP1.1

HTTP 1.1 是1997年1月发布,在 1.0 发布之后的半年就推出了,完善了 1.0 版本。目前也还有很多的互联网项目基于 HTTP 1.1 在向外提供服务。

  • 长连接:新增 Connection 字段,可以设置 keep-alive 值保持连接不断开

  • 管道化:基于上面长连接的基础,管道化可以不等第一个请求响应继续发送后面的请求,但响应的顺序还是按照请求的顺序返回

基于长连接的基础,没有管道化的请求响应

请求1 > 响应1 -->请求2 > 响应2 --> 请求3 > 响应3

管道化的请求响应

请求1 --> 请求2 --> 请求3 > 响应1 --> 响应2 --> 响应3

虽然管道化可以一次发送多个请求,但是响应仍是按照顺序返回的,还是无法解决队头阻塞的问题。

  • 缓存处理:新增字段 Cache-Control、Etag / If-None-Match来读取缓存

  • 断点传输:在上传/下载资源的时候,如果资源过大,将其分割为多个部分,分别上传/下载。如果遇到网络故障,可以从已经上传/下载好的地方继续请求,不用从头开始,提高效率。

4. HTTP2

http2大幅度的提升了web性能,完全兼容http1.1完全语意兼容。

  • 二进制分帧:http1.1的头信息是文本,数据体可以是文本,也可以是二进制;http2的头信息和数据体都是二进制,统称是‘帧’(头信息帧和数据帧),提高传输效率。

为了保证HTTP不受影响,增加了二进制分帧层,在二进制分帧层上,HTTP2会将所有传输的信息分为更小的信息和帧,并采用二进制格式编码,其中HTTP1.x的首部信息会被封装到Header帧,而Request Body则封装到Data帧中。

  • 多路复用(双工通信)

在HTTP1.x中,浏览器限制同一域名下的请求数量,当页面请求很多资源的时候,队头阻塞会导致在最大请求时,资源需要等待其他资源请求完成后才能继续发送。

基于二进制分帧层,HTTP2.可以在共享TCP连接的基础上同时发送请求和响应。HTTP消息被分解成独立的帧,不破坏消息本身的语义,交错发出,在另一端根据流标识符和首部将他们重新组装起来。通过该技术,避免队头阻塞问题,极大的提高了传输性能。

  • 首部压缩

HTTP每次请求都必须带有所有信息,所以很多的字段都是重复的,比如Cookie和User Agent,一摸一样的内容,这会浪费很多的带宽也会影响速度。

HTTP2对这一点做了优化,引入了头部压缩机制。一方面,头信息使用gzip或者compress压缩后发送;另一方面,客户端和服务器端同时维护一张头信息表,所有的字段都存入这个表,生成一个索引号。

  • 数据流

HTTP2的数据包不是按顺序发送的,同一个连续的数据包,可能属于不同的回应,因此要对数据包做标记,指出属于哪个回应。请求或回应的所有数据包,都称为一个数据流,每个数据流都是独一无二的编号。数据包发送的时候,都必须标记数据流ID,用来区分它属于哪个数据流。另外还规定,客户端发出的数据流ID为奇数,服务器端发送的ID为偶数。

数据流发送一半的时候,客户端和服务器端都可以发送信号(RST_STREAM帧),取消这个数据流。HTTP1.1取消数据流的唯一方法就是关闭TCP连接。但是HTTP2可以取消某一次请求,同时保证TCP连接还打开,还能够被其他请求使用。

  • 服务器推送

服务器可以对一个客户端发送多个响应。服务器端根据客户端的请求,提前返回多个响应,推送额外的资源给客户端。如下图,客户端请求stream1。服务器在返回stream1的消息的同时推送了饿stream2和stream4

推送的缺点:所有的推送必须遵循同源策略。换句话说,服务器不能随便将第三方资源推送给客户端,必须是经过双方确认才行。

2、缓存介绍

1. 什么是缓存

在实际的开发过程中,缓存技术会涉及到不同层、不同端。每一层的缓存目标都是一致的,就是尽快返回数据请求,减少延迟,但是每层使用的技术实现是不相同的。

当浏览器请求一个网站的时候,会加载各种各样的资源,比如HTML文档、图片、css、js等文件,对于一些不经常变的内容,浏览器会将他们保存在本地文件中,下次访问相同的网站时候直接加载这些资源,加速访问。这些被浏览器保存的文件就被称为缓存(不是指Cookie和Localstorage)

2. 缓存的位置

a. Service Worker

Service worker 本质上充当 Web 应用程序、浏览器与网络(可用时)之间的代理服务器。这个 API 旨在创建有效的离线体验,它会拦截网络请求并根据网络是否可用采取来适当的动作、更新来自服务器的的资源。它还提供入口以推送通知和访问后台同步 API。

Service Worker就是一个全新的javascript线程,运行在和主线程不同的上下文。主线程负责DOM的线程,所以Service Worker就被设计成不能无法访问DOM。这是很正常的,UI线程只能存在一个,否则整个UI的控制就会出现不可预估的问题。

b. Memory Cache

内存中的缓存,主要包含当前页面中已经抓取到的资源。比如页面上已经下载的样式、脚本、图片等。读取内存中的数据肯定比磁盘的快,虽然读取高效,但是缓存的持续时间短,会随着进程的释放而释放。一旦我们关闭Tab页,内存中的缓存也就会被释放。

❓ 什么时候资源会被放入Memory cache中呢?

几乎所有的网络请求资源都会根据相关的策略被浏览器自动放入memory cache中,不受控制相当于一个黑盒。但是也正因为数量很大但是浏览器占用的内存不能无限扩大这样两个因素,memory cache 注定只能是个“短期存储”。当数据量过大,即使网页不关闭,缓存依然会失效。

❓ 既然内存缓存如此高效,为什么不将所有的数据都放在内存缓存中呢?

因为相对于磁盘容量,内存要小很多,系统使用内存都要精打细算,所以我们使用的不多。

c. Disk Cache

存储在硬盘中的缓存,读取速度慢,但是什么都能存储到磁盘当中,与内存相比胜在容量和存储的时效性上。MemoryCache要比DiskCache快的多。举个例子,从远程web服务器直接提取访问文件可能需要500毫秒,那么磁盘访问可能需要10-20毫秒,而内存访问只需要100纳秒。

在所有的浏览器缓存中,Disk Cache覆盖面是最大的,它会根据HTTP Hearder中的字段判断哪些资源需要缓存,哪些资源可以不请求直接使用,哪些资源过期了需要重新请求。并且即使在跨域的情况下,相同地址的资源一旦被硬盘缓存下来,就不会再次去请求数据了。

d. Push Cache

Push Cache(推送缓存)是HTTP/2中的内容,当以上三种缓存都没有命中时,才会使用。Push Cache只在会话中存在,一旦会话结束就会释放,并且缓存的时间也很短暂。在chrome里只有5分钟左右。

3. 缓存的过程

浏览器和服务器之间的通信方式:浏览器发起HTTP请求-服务器响应该请求

根据上图可知:

  1. 浏览器发起请求,会在浏览器缓存中查找该请求的结果和缓存标识;

  2. 浏览器每次拿到返回的结果会将这个结果和缓存标识存储下来。

4. 强缓存和协商缓存

浏览器会先去查看强缓存 (Expires 和 cache-control) 判断是否过期,如果强缓存生效,返回 200 并从缓存中读取资源;若不生效则进行协商缓存 (Last-Modified / If-Modified-Since 和 Etag / If-None-Match),协商缓存由服务器决定是否使用缓存,若协商缓存失效,那么代表该请求的缓存失效,返回 200,并重新返回资源和缓存标识,再次存入浏览器缓存中;生效则返回 304,并从缓存中读取资源 (协商缓存之前要经过 DNS 域名解析,之后建立 TCP 链接)

4.1 强缓存

强缓存有两个相关字段:Expires、Cache-Control

HTTP1.0版本:使用的是Expires

HTTP1.1版本:使用的是Cache-Control

1. Expires:缓存过期时间,用来指定资源到期时间,是服务器端的具体时间点。也就是说Expires=max-age+请求时间。Cache-Control的优先级高于Expires。Expires是文本服务器端响应消息字段,在响应http请求时告诉浏览器在过期时间前浏览器可以从浏览器缓存中读取数据,而无需再次请求。

但是这样就会有新的问题,服务器时间和浏览器时间可能会不一致,所以HTTP1.1提出新的字段替代它。

2. Cache-Control主要包括

  • max-age

  • s-maxage

  • public

  • private

  • no-cache

  • no-store

  • must-revalidate

max-age:单位为s,指定设置缓存最大的有效时间,定义的是时间的长短。当浏览器向服务器发送请求后,在max-age这段时间里浏览器就不会再向服务器发送请求了。

s-maxage: 单位为s,同max-age,只用于共享缓存(比如CDN缓存)。

public:指定响应会被缓存,并且在多用户之间共享。

private:响应只作为私有的缓存,不能在用户之间共享。

no-cache:不缓存过期资源,缓存会向服务器进行有效确认之后处理资源。

设置no-cache之后并不代表浏览器不缓存,而是在缓存之前要向服务器确认资源是否被更改。因此有的时候只设置no-cache防止缓存是不够保险的,还可以加上private,将过期时间设置为过去时间。

no-store:绝对禁止缓存。每次请求资源都要从服务器重新获取。

must-revalidate:如果页面过期,则去服务器进行获取。

4.2 协商缓存

协商缓存就是强制缓存失效后,浏览器携带缓存标识向服务器发起请求,由服务器根据缓存标识决定是否使用缓存的过程。

协商缓存的标识也是在响应报文的HTTP头中和请求结果一起返回给浏览器的,控制协商缓存的字段分别有:

HTTP1.0版本:Last-Modified / If-Modified-Since

HTTP1.1版本:Etag / If-None-Match,

其中Etag / If-None-Match的优先级比Last-Modified / If-Modified-Since高。

1. Last-Modified / If-Modified-Since

Last-Modified是服务器响应请求时,返回该资源文件在服务器最后被修改的时间。

If-Modified-Since则是客户端再次发起该请求时,携带上次请求返回的Last-Modified值,通过此字段值告诉服务器该资源上次请求返回的最后被修改时间。服务器收到该请求,发现请求头含有If-Modified-Since字段,则会根据If-Modified-Since的字段值与该资源在服务器的最后被修改时间做对比,若服务器的资源最后被修改时间大于If-Modified-Since的字段值,则重新返回资源,状态码为200;否则则返回304,代表资源无更新,可继续使用缓存文件。

如果只是查看了文件Last-Modified也会被修改,而且一秒之内多次修改是没有办法获取到最后修改的文件。为了解决这个问题,HTTP1.1增加了新的字段Etag / If-None-Match。

2. Etag / If-None-Match

Etag是服务器响应请求时,返回当前资源文件的一个唯一标识(由服务器生成)。

If-None-Match是客户端再次发起该请求时,携带上次请求返回的唯一标识Etag值,通过此字段值告诉服务器该资源上次请求返回的唯一标识值。服务器收到该请求后,发现该请求头中含有If-None-Match,则会根据If-None-Match的字段值与该资源在服务器的Etag值做对比,一致则返回304,代表资源无更新,继续使用缓存文件;不一致则重新返回资源文件,状态码为200。

Etag / If-None-Match优先级高于Last-Modified / If-Modified-Since,同时存在则只有Etag / If-None-Match生效。

5. 缓存都是好的吗?

  1. 正常重新加载:

直接刷新当前页面,会正常走浏览器检查缓存的流程。

  1. 硬性重新加载:

浏览器发送请求的时候,会带上禁止缓存的请求头,并且不会使用缓存的资源而是重新请求数据。

  1. 清空缓存并硬性重新加载:

浏览器除了会执行硬性重新加载还会清除缓存内容。避免一些js文件读取缓存。

  1. Disable cache

打开Disable cache浏览器不会删除本地的缓存,也不会做强缓存。

  1. Webpack打包生成的文件

文件名+contenthash

如果源码不变,打包生成文件的contenthash是不会变化的。

  1. 缓存控制

还有更多⌛️

微信的缓存加meta标签不生效。打包时加上contenthash。

3、总结

浏览器在第二次以后访问就能够使用缓存资源,不必每次都向服务器请求,加快了访问速度。本着能用就用,不能用就商量着用的原则来使用缓存资源。

参考资料

juejin.cn/post/694793…

www.javashuo.com/article/p-t…

www.jianshu.com/p/54cc04190…

猜你喜欢

转载自juejin.im/post/7033590153301983239