前端缓存问题

439 阅读7分钟

前端缓存主要分为HTTP缓存和浏览器缓存,见名知其意,HTTP缓存就是请求过程中的使用的缓存,主要是设置在request和responce头中,浏览器缓存主要是由前端js来设置。这里先讲http缓存,等有空了,再补浏览器缓存。

缓存是性能优化中一种重要的优化方式,一个优秀的缓存策略可以缩短网页请求资源的距离,减少带宽资源,降低负荷等。

对于一个数据请求主要分为三个阶段,发起请求、服务端处理、浏览器响应,浏览器可以在一三步骤里面优化性能,比如直接使用缓存,不发送请求,或者发起了请求,但是经验证服务端和浏览器端的数据一致,那么久没有必要返回数据,直接使用缓存数据,久减少了响应。

缓存的过程

当浏览器发起HTTP请求的时候,会先去浏览器缓存中查找,当没有该请求的缓存结果或者缓存标识的时候,会去向服务器发起HTTP请求,服务器会返回请求结果和缓存规则(在响应头里),浏览器每次会将该请求结果和缓存标识存入浏览器缓存中

强缓存(http:200)

强缓存是向浏览器缓存查找该请求的结果,并根据请求回来的结果的缓存规则来决定是否使用这个缓存结果,强缓存主要有三种情况: 1.当发起第一次http请求的时候,浏览器先去查询是否存在缓存结果和缓存标识,发现不存在,则强缓存失效,直接向服务器发起请求 2.当发起http请求的时候,浏览器查询到缓存中存在缓存结果和缓存标识,但该缓存结果已失效,强缓存失效,使用协商缓存(即带着缓存标识,向服务端发起HTTP请求) 3.当发起HTTP请求,命中强缓存,不会再向服务器发送请求,http直接返回200状态码 size from cache(或者from memory),直接返回结果

强缓存的缓存规则

强缓存的规则保存在服务端对于请求的响应的响应头的字段中
控制强缓存的字段 Expires和Cache-Control,其中Cache-Control优先级比Expires高。

Expires的原理是对比客户端和服务端的时间,并不可靠(客户端时间可以调整的,调整到expire时间之外,缓存就失效了)
Cache-Control在request header与response header中都支持这个属性
max-age 缓存周期,单位是秒,时间是相对于请求时间的,避免了expire的问题
可以缓存的指令:no-cache/public/private/only-if-cached
过期的指令:min-fresh
重新验证重新加载指令:must-revalidate/proxy-revalidate
其他no-transform/no-store

协商缓存(http:304)

发送http请求的时候,先去查找强缓存,发现缓存结果和标识,但是缓存结果失效,就要使用协商缓存,于是发起http请求,http请求服务器的时候会带上缓存标识,服务器接收到缓存标识,进行对比,发现服务器内容没有改变,于是响应返回,响应304,告诉浏览器可以使用缓存

协商缓存有两种标识:(Etag / If-None-Match的优先级比Last-Modified / If-Modified-Since高)

Last-Modified/If-Modified-Since:Last-Modified属性告诉浏览器,这个资源最后的修改时间,当浏览器再次请求的时候,会在request header中加入If-Modified-Since属性,值为上次返回的Last-Modified的时间,服务器匹配,匹配成功,就会返回304,继续使用缓存;如果匹配发现时间不一致,不成功,返回新的完整的资源和200状态码,并在响应头中带上Last-Modified,值为最新的修改时间;
Etag / If-None-Match If-None-Match的功能类似于If-Modified-Since都是在请求头中携带和服务器中当前的Etag比对,如果相同的话,命中协商缓存
1)Etag是服务器资源的唯一标识符,
2)如果内容发生了变化,使用ETag有助于防止资源的同时更新相互覆盖(空中碰撞)/这里怎么防止的暂时没有找到资料说明/
3)Last-Modified标注的时间只能精确到秒,如果一个资源在一秒内被多次修改,Etag可以标注出他的修改,Last-Modified则不可以
最后盗张图 图片来源地址

vue项目的index.html缓存问题

问题

  vue项目打包后,在非首次线上替换dist文件时,某些手机/浏览器在之后首次打开页面,可能出现白屏情况。那么该问题产生原因是什么呢?我们又该如何应对呢?

背景及原因分析

  在使用vue-cli脚手架构建完项目,项目完成后,需打包上线。默认打包方式则是 npm run build,然后项目根目录会生成 dist 文件夹。服务端将该文件夹替换线上即可。但是当第n(n>1)次上线后,由于在用户端会默认缓存index.html入口文件,而由于vue打包生成的css/js都是哈希值,跟上次的文件名都不同,因此会出现找不到css/js的情况,导致白屏的产生。

  经常使用vue作为开发框架的开发者都知道,build打包后,所生成的css/js的文件名中间会夹杂哈希值,以此来避免缓存问题。但是由于入口文件index.html的名字每次打包后不改变,并且也不能乱变,就导致了index.html在用户端仍然会被缓存下来。那么在服务端更新包之后,由于旧的文件被删除,而index.html所链接的路径依然是旧文件路径,因此会找不到文件,从而白屏。解决方案一般是强制刷新页面或者清除缓存重新加载。当然,网上也给出不少的“缓解方案”。为什么称之为缓解方案而不是解决方案呢?因为前端缓存问题是一个具有行业性的难题,在没有根治之前,一般是优化为主。当然,引领前端行业的大佬们自然是要挑战极限的,不在该问题之内讨论。  下面给出部分优化方案。

  • 方案一 ng配置

    众所周知,vue项目的js和css打包后的文件名称是哈希文件名,不会重复,但是有时候会碰到一个现象就是升级版本后,index.html发生了缓存,这样整个项目都会出现缓存问题,解决这个问题的方案就是在nginx中加上一项配置来解决。

server {
listen 80;
server_name yourdomain.com;
location / {
    try_files $uri $uri/ /index.html;
    root /yourdir/;
    index index.html index.htm;

    if ($request_filename ~* .*\.(?:htm|html)$)
    {
        add_header Cache-Control "no-cache, no-store";  //对html文件设置永远不缓存
    }  
  }
}

该配置的大概意思是不缓存过期资源、不缓存,这样就会将index.html缓存的问题解决掉。

  • 方案2 设置meta标签

在 head 里面添加下面代码,ding

<meta http-equiv="pragram" content="no-cache">
<meta http-equiv="cache-control" content="no-cache, no-store, must-revalidate">

这种会让所有的css/js资源重新加载

  • 方案3 chunkhash、contenthash
 output: { 
     path: config.build.assetsRoot, 
     filename: utils.assetsPath('js/[name].[chunkhash].'+'js'), 
     chunkFilename: utils.assetsPath('js/[id].[chunkhash].'+'js')
 }

方案对比

方案操作难度优缺点
1服务端加配置,简单解决部分缓存问题,不解决全部;白屏问题依旧存在
2html文件简单修改用处不大,基本没用
3webpack配置简单修改跟hash值文件名一个性质,不解决痛点
新思路

  在某些特定情况下(如混合开发App,原生嵌入webview还在h5),可在原生端尝试解决:

  • 加载webview前清除缓存再加载;
  • 销毁webview前清除缓存;

我们可以设置开关,定期在前端发包前后打开,开启3-5天之后,自动关闭