浏览器缓存

什么是浏览器缓存?

简单来说,浏览器缓存就是把一个已经请求过的Web资源(如html页面,图片,js,数据等)拷贝一份副本储存在浏览器中。缓存会根据进来的请求保存输出内容的副本。当下一个请求来到的时候,如果是相同的URL,缓存会根据缓存机制决定是直接使用副本响应访问请求,还是向源服务器再次发送请求。比较常见的就是浏览器会缓存访问过网站的网页,当再次访问这个URL地址的时候,如果网页没有更新,就不会再次下载网页,而是直接使用本地缓存的网页。只有当网站明确标识资源已经更新,浏览器才会再次下载网页。至于浏览器和网站服务器是如何标识网站页面是否更新的机制,将在后面介绍。

为什么要用浏览器缓存?

(1)减少网络带宽消耗

(2)降低服务器压力

(3)减少网络延迟,加快页面打开速度

浏览器端的缓存规则:

  对于浏览器端的缓存来讲,这些规则是在HTTP协议头和HTML页面的Meta标签中定义的。他们分别从新鲜度校验值两个维度来规定浏览器是否可以直接使用缓存中的副本,还是需要去源服务器获取更新的版本。

  新鲜度(过期机制):也就是缓存副本有效期。一个缓存副本必须满足以下条件,浏览器会认为它是有效的,足够新的:

    1. 含有完整的过期时间控制头信息(HTTP协议报头),并且仍在有效期内;

    2. 浏览器已经使用过这个缓存副本,并且在一个会话中已经检查过新鲜度;

  满足以上两个情况的一种,浏览器会直接从缓存中获取副本并渲染。

  校验值(验证机制):服务器返回资源的时候有时在控制头信息带上这个资源的实体标签Etag(Entity Tag),它可以用来作为浏览器再次请求过程的校验标识。如过发现校验标识不匹配,说明资源已经被修改或过期,浏览器需求重新获取资源内容。

浏览器缓存的控制:

  (1)使用HTML Meta 标签

  Web开发者可以在HTML页面的<head>节点中加入<meta>标签,代码如下

<meta http-equiv="Pragma" content="no-cache">  
<!- Pragma是http1.0版本中给客户端设定缓存方式之一,具体作用会在后面详细介绍 -->

  上述代码的作用是告诉浏览器当前页面不被缓存,每次访问都需要去服务器拉取。但是!这里有个坑...

  事实上这种禁用缓存的形式用处很有限:

    a. 仅有IE才能识别这段meta标签含义,其它主流浏览器仅识别“Cache-Control: no-store”的meta标签。

    b. 在IE中识别到该meta标签含义,并不一定会在请求字段加上Pragma,但的确会让当前页面每次都发新请求(仅限页面,页面上的资源则不受影响)

  (2)使用缓存有关的HTTP消息报头

  在HTTP请求和响应的消息报头中,常见的与缓存有关的消息报头有:

规则 消息报头 值/示例 类型 作用
新鲜度 Pragma no-cache 响应 告诉浏览器忽略资源的缓存副本,每次访问都需要去服务器拉取【http1.0中存在的字段,在http1.1已被抛弃,使用Cache-Control替代,但为了做http协议的向下兼容,很多网站依旧会带上这个字段】
  Expires  Mon, 15 Aug 2016 03:56:47 GMT 响应 启用缓存和定义缓存时间。告诉浏览器资源缓存过期时间,如果还没过该时间点则不发请【http1.0中存在的字段,该字段所定义的缓存时间是相对服务器上的时间而言的,如果客户端上的时间跟服务器上的时间不一致(特别是用户修改了自己电脑的系统时间),那缓存时间可能就没啥意义了。在HTTP 1.1版开始,使用Cache-Control: max-age=秒替代】
   Cache-Control no-cache  请求/响应 告诉浏览器忽略资源的缓存副本,强制每次请求直接发送给服务器,拉取资源,但不是“不缓存”
    no-store 响应  强制缓存在任何情况下都不要保留任何副本
     max-age=[秒] 响应 指明缓存副本的有效时长,从请求时间开始到过期时间之间的秒数
    must-revalidate 响应 如果超过了 max-age 的时间,浏览器必须向服务器发送请求,验证资源是否还有效。
     public 响应 相应内容经过的路径的任何缓存者(本地缓存、代理服务器),可以无条件的缓存该资源
     private 响应 只针对单个用户或者实体(不同用户、窗口)缓存资源
   Last-Modified Mon, 15 Aug 2016 03:56:47 GMT 响应 告诉浏览器这个资源最后的修改时间。服务器将资源传递给客户端时,会将资源最后更改的时间以“Last-Modified: GMT”的形式加在实体首部上一起返回给客户端【只能精确到秒级,如果某些文件在1秒钟以内,被修改多次的话,它将不能准确标注文件的修改时间
   If-Modified-Since Mon, 15 Aug 2016 03:56:47 GMT  请求 其值为上次响应头的Last-Modified值,再次向web服务器请求时带上头If-Modified-Since。web服务器收到请求后发现有头If-Modified-Since则与被请求资源的最后修改时间进行比对。若最后修改时间较新,说明资源又被改动过,则响应整片资源内容(写在响应消息包体内),包括更新Last-Modified的值,HTTP 200;若最后修改时间较旧,说明资源无新修改,则响应HTTP 304(无需包体,节省浏览),告知浏览器继续使用所保存的cache
校验值 ETag "fd56273325a2114818df4f29a628226d"  响应 告诉浏览器当前资源在服务器的唯一标识符(生成规则又服务器决定)
  If-None-Match  "fd56273325a2114818df4f29a628226d" 请求 当资源过期时(使用Cache-Control标识的max-age),发现资源具有Etage声明,则再次向web服务器请求时带上头If-None-Match(Etag的值)。web服务器收到请求后发现有头If-None-Match则与被请求资源的相应校验串进行比对,决定返回200或304

   · Cache-Control与Expires

  Cache-Control与Expires的作用一致,都是指明当前资源的有效期,控制浏览器是否直接从浏览器缓存取数据还是重新发请求到服务器取数据。只不过Cache-Control的选择更多,设置更细致,如果同时设置的话,Cache-Control优先级高于Expires

  · Last-Modified/ETag与Cache-Control/Expires

  配置Last-Modified/ETag的情况下,浏览器再次访问统一URI的资源,还是会发送请求到服务器询问文件是否已经修改,如果没有,服务器会只发送一个304回给浏览器,告诉浏览器直接从自己本地的缓存取数据;如果修改过那就整个数据重新发给浏览器;

  Cache-Control/Expires则不同,如果检测到本地的缓存还是有效的时间范围内,浏览器直接使用本地副本,不会发送任何请求。两者一起使用时,Cache-Control/Expires的优先级要高于Last-Modified/ETag。即当本地副本根据Cache-Control/Expires发现还在有效期内时,则不会再次发送请求去服务器询问修改时间(Last-Modified)或实体标识(Etag)了。

  一般情况下,使用Cache-Control/Expires会配合Last-Modified/ETag一起使用。因为即使服务器设置缓存时间, 当用户点击“刷新”按钮时,浏览器会忽略缓存继续向服务器发送请求,这时Last-Modified/ETag将能够很好利用304,从而减少响应开销。

   · Last-Modified与ETag

  你可能会觉得使用Last-Modified已经足以让浏览器知道本地的缓存副本是否足够新,为什么还需要Etag(实体标识)呢?HTTP1.1中Etag的出现主要是为了解决几个Last-Modified比较难解决的问题:

  1. Last-Modified标注的最后修改只能精确到秒级,如果某些文件在1秒钟以内被修改多次的话,它将不能准确标注文件的新鲜度
  2. 如果某些文件会被定期生成,有时内容并没有任何变化,但Last-Modified却改变了,导致文件没法使用缓存(Etag不变)
  3. 有可能存在服务器没有准确获取文件修改时间,或者与代理服务器时间不一致等情形

Last-Modified与ETag是可以一起使用的,服务器会优先验证ETag,一致的情况下,才会继续比对Last-Modified,最后才决定是否返回304。

 

1. Etag是服务器自动生成或者由开发者生成的对应资源在服务器端的唯一标识符,能够更加准确的控制缓存。但是需要注意的是分布式系统里多台机器间文件的last-modified必须保持一致,以免负载均衡到不同机器导致比对失败。

2. Last-Modified/If-Modified-Since要配合Cache-Control使用,Etag/If-None-Match也要配合Cache-Control使用。

 

浏览器HTTP请求流程:

 

  第一次请求:

 

 

  

 

  再次请求:

 

  

 

前端缓存最佳实践:

1. 缓存分为强缓存协商缓存两种。

   强缓存:一般,我们会设置Cache-Control的值为“public, max-age=xxx”,表示在xxx秒内再次访问该资源,均使用本地的缓存,不再向服务器发起请求。

  显而易见,如果在xxx秒内,服务器上面的资源更新了,客户端在没有强制刷新的情况下,看到的内容还是旧的。如果发布新版本的时候,后台接口也同步更新了,有缓存的用户还在使用旧接口,但那个接口已经被后台干掉了,那就gg了。

  协商缓存:每次都要向服务器验证一下缓存的有效性,似乎看起来很省事,不管那么多,你每次都要问一下我是否有效。

缓存的意义就在于减少请求,更多地使用本地的资源,给用户更好的体验的同时,也减轻服务器压力。

所以,最佳实践,就应该是尽可能命中强缓存,同时,能在更新版本的时候让客户端的缓存失效

解决方法,在更新版本的时候,使用文件名+hash值的方式进行判断。(webpack可以让我们在打包的时候,在文件的命名上带上hash值。)

entry:{
    main: path.join(__dirname,'./main.js'),
    vendor: ['react', 'antd']
},
output:{
    path:path.join(__dirname,'./dist'),
    publicPath: '/dist/',
    filname: 'bundle.[chunkhash].js'
}

综上所述,我们可以得出一个较为合理的缓存方案:

  • HTML:使用协商缓存
  • CSS&JS&图片:使用强缓存,文件命名带上hash值

hash、chunkhash和contenthash。那么这三者有什么区别呢?

  • hash:跟整个项目的构建相关,构建生成的文件hash值都是一样的,只要项目里有文件更改,整个项目构建的hash值都会更改。
  • chunkhash:根据不同的入口文件(Entry)进行依赖文件解析、构建对应的chunk,生成对应的hash值。
  • contenthash:由文件内容产生的hash值,内容不同产生的contenthash值也不一样。
我们一般会把项目中的css都抽离出对应的css文件来加以引用。如果我们使用chunkhash,当我们改了css代码之后,会发现css文件hash值改变的同时,js文件的hash值也会改变。这时候,contenthash就派上用场了。

后端如何设置
强缓存
server.setHeader('Cache-Control', 'public, max-age=xxx');

协商缓存

sever.setHeader('Cache-Control', 'public, max-age=0');
sever.setHeader('Last-Modified', xxx);
sever.setHeader('ETag', xxx);

 

posted @ 2019-03-27 17:00  cecelia  阅读(218)  评论(0编辑  收藏  举报