如何让浏览器不缓存文件

更新日期: 2022-05-14阅读: 1.1k标签: 缓存

前言

最近在项目开发中遇到一个需求:项目打包后,可以根据修改配置文件,进而动态替换页面上的文本。由于项目基本不涉及到后端,因此不考虑通过接口来修改。这就需要前端项目打包后需要暴露一个配置文件,每次页面刷新时会获取到最新的配置,达到动态替换页面文本的目的。

本文重点总结下如何可以让浏览器不缓存静态资源,保证每次获取的都是最新的资源。

浏览器缓存

想知道如何不缓存文件,就需要先了解浏览器是怎么判断是否要缓存文件的。这里要引出一个概念,那就是浏览器缓存。

浏览器缓存(Brower Caching)是浏览器在本地磁盘对用户最近请求过的文档进行存储,当访问者再次访问同一页面时,浏览器就可以直接从本地磁盘加载文档。

「浏览器缓存的优点有:」

  • 减少了冗余的数据传输,节省了网费

  • 减少了服务器的负担,大大提升了网站的性能

  • 加快了客户端加载网页的速度

浏览器缓存主要有两类:缓存协商和彻底缓存,也有称之为 「协商缓存」 和 「强缓存」 。

浏览器在第一次请求发生后,再次请求时:

  1. 浏览器会先获取该资源缓存的header信息,根据其中的 Expires 和 Cache-control 判断是否命中强缓存,若命中则直接从缓存中获取资源,包括缓存的header信息,本次请求不会与服务器进行通信;
  2. 如果没有命中强缓存,浏览器会发送请求到服务器,该请求会携带第一次请求返回的有关缓存的header字段信息( Last-Modified/IF-Modified-Since 、 Etag/IF-None-Match ),由服务器根据请求中的相关header信息来对比结果是否命中协商缓存,若命中,则服务器返回新的响应header信息更新缓存中的对应header信息,但是并不返回资源内容,它会告知浏览器可以直接从缓存获取;否则返回最新的资源内容。

强缓存

与强缓存相关的头部有两个,分别是 Cache-Control 与 Expires 。

这里重点介绍下 Cache-Control 。

「Cache-Control」

Cache-Control 是 http1.1 时出现的 header 信息,主要是利用该字段的 max-age 值来进行判断,它是一个相对时间,例如

  • Cache-Control:max-age=3600 ,代表着资源的有效期是3600秒。

Cache-control 除了该字段外,还有下面几个比较常用的设置值:

  • no-cache :不使用本地缓存。需要使用缓存协商,先与服务器确认返回的响应是否被更改,如果之前的响应中存在ETag,那么请求的时候会与服务端验证,如果资源未被更改,则可以避免重新下载。
  • no-store :直接禁止游览器缓存数据,每次用户请求该资源,都会向服务器发送一个请求,每次都会下载完整的资源。
  • public :可以被所有的用户缓存,包括终端用户和CDN等中间代理服务器。
  • private :只能被终端用户的浏览器缓存,不允许CDN等中继缓存服务器对其缓存。

下次当遇到面试官问 no-cache 和 no-store 的区别时,应该知道怎么回答了吧。

注意, Cache-Control 与 Expires 可以在服务端配置同时启用,同时启用的时候 Cache-Control 优先级高。建议使用 Cache-Control 。

协商缓存

协商缓存有个特点,就是响应头和请求头是成双成对出现的。第一次请求资源时,浏览器会返回响应头;再次请求资源时,浏览器会添加相应的请求头。具体来说,是 「Last-Modify/If-Modify-Since」 和 「ETag/If-None-Match」 。

被浏览器缓存的文件会有不同的缓存来源,包括 from memory cache 和 from disk cache ,前者指缓存来自内存,后者指缓存来自硬盘。决定缓存到内存还是硬盘的正是 Etag 字段。如果响应头有 Etag 字段,那么浏览器就会将本次缓存写入硬盘中。

「Last-Modify/If-Modify-Since」

浏览器第一次请求一个资源的时候,服务器返回的header中会加上 Last-Modify , Last-modify 是一个时间标识该资源的最后修改时间,例如Last-Modify: Thu,31 Dec 2037 23:59:59 GMT。

当浏览器再次请求该资源时,request的请求头中会包含 If-Modify-Since ,该值为缓存之前返回的 Last-Modify 。服务器收到 If-Modify-Since 后,根据资源的最后修改时间判断是否命中缓存。

如果命中缓存,则返回304,并且不会返回资源内容,并且不会返回 Last-Modify 。

需要注意的是, If-Modified-Since 只可以用在 GET 或 HEAD 请求中。

「ETag/If-None-Match」

与 Last-Modify/If-Modify-Since 不同的是, Etag/If-None-Match 返回的是一个校验码。 ETag 可以保证每一个资源是唯一的,资源变化都会导致 ETag 变化。服务器根据浏览器上送的 If-None-Match 值来判断是否命中缓存。

ETag HTTP响应头是资源的特定版本的标识符。这可以让缓存更高效,并节省带宽,因为如果内容没有改变,Web服务器不需要发送完整的响应。而如果内容发生了变化,使用 ETag 有助于防止资源的同时更新相互覆盖。

与 Last-Modified 不一样的是,当服务器返回 304 Not Modified 的响应时,由于 ETag 重新生成过,response header 中还会把这个 ETag 返回,即使这个 ETag 跟之前的没有变化。

If-None-Match 是一个条件式请求首部。对于 GET 和 HEAD 请求方法来说,当且仅当服务器上没有任何资源的 ETag 属性值与这个首部中列出的相匹配的时候,服务器端才会返回所请求的资源,响应码为 200 。对于其他方法来说,当且仅当最终确认没有已存在的资源的 ETag 属性值与这个首部中所列出的相匹配的时候,才会对请求进行相应的处理。

当与 If-Modified-Since 一同使用的时候, If-None-Match 优先级更高(假如服务器支持的话)。

「强缓存与协商缓存的区别」

缓存类型获取资源形式状态码发送请求到服务器
强缓存从缓存取200(from cache)否,直接从缓存取
协商缓存从缓存取304(Not Modified)否,通过服务器来告知缓存是否可用

「用户行为对缓存的影响」

用户操作Expires/Cache-ControlLast-Modied/Etag
地址栏回车有效有效
页面链接跳转有效有效
新开窗口有效有效
前进回退有效有效
F5刷新无效有效
Ctrl+F5强制刷新无效无效

参考链接: https://zxpsuper.github.io/Demo/advanced_front_end/browser/cache.html 

不缓存

no-store

上面介绍了一下浏览器缓存文件的方式,其中提到强制缓存的 Cache-control 的指令 no-store ,作用是不存储有关客户端请求或服务器响应的任何内容,即不使用任何缓存。

需要注意, Cache-Control 是通用消息头字段,既可以用于请求头,也可以用于响应头。

发送如下响应头可以关闭缓存:

Cache-Control: no-store

这里额外引用MDN里的几个示例,说明下其他场景该如何配置。

「缓存静态资源」

对于应用程序中不会改变的文件,你通常可以在发送响应头前添加积极缓存。这包括例如由应用程序提供的静态文件,例如图像,css文件和JavaScript文件。

Cache-Control:public, max-age=31536000

「需要重新验证」

指定 no-cache 或 max-age=0, must-revalidate 表示客户端可以缓存资源,每次使用缓存资源前都必须重新验证其有效性。这意味着每次都会发起 HTTP 请求,但当缓存内容仍有效时可以跳过 HTTP 响应体的下载。

Cache-Control: no-cache
Cache-Control: max-age=0, must-revalidate

「注意」: 如果服务器关闭或失去连接,下面的指令可能会造成使用缓存。

Cache-Control: max-age=0

增加版本号

这种方法不需要依赖服务端,纯前端便可实现。该方法流行于前端工程化诞生之前,弊端是需要手动增加版本号,人为干预较多。

<script type="text/javascript" src="../js/jquery.min.js?version=1.7.2" ></script>

使用随机数

既然在文件后面添加指纹可以让浏览器重新获取资源,那么我们可以在后面拼接随机数或者时间戳,这样也可以达到相同的目的,还省去了手动更改版本号的步骤。

具体来说,可以在 index.html 增加一段脚本,用来动态生成一个script标签,并引入静态资源,拼接时间戳。

var script = document.createElement("script");
script.src = "/resource/options/myjs.js?randomId=" + new Date().getTime();
document.body.appendChild(script);

这样浏览器每次刷新后,便会动态生成一个包含时间戳的静态资源。浏览器发现文件名有更改,会重新获取静态资源,达到了不缓存文件的目的。

使用HTML禁用缓存

HTML也可以禁用缓存, 即在页面的head标签中加入meta标签。例:

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

说明:虽能禁用缓存,但只有部分浏览器支持,而且由于代理不解析HTML文档,故代理服务器也不支持这种方式。该方法不适用于特定文件不缓存的要求。

应用

掌握了以上缓存与不缓存的方式,接下来该进行实战了。

正在开发的项目使用的 Vite , Vite 使用 .env 文件来保存额外的环境变量:

.env                # 所有情况下都会加载
.env.local # 所有情况下都会加载,但会被 git 忽略
.env.[mode] # 只在指定模式下加载
.env.[mode].local # 只在指定模式下加载,但会被 git 忽略

一份用于指定模式的文件(例如 .env.production )会比通用形式的优先级更高(例如  .env )。 .env 类文件会在 Vite 启动一开始时被加载,而改动会在重启服务器后生效。

那么可以考虑将需要动态替换的文本配置放入 .env 文件,并在打包的时候,将 .env 文件的配置暴露出去成为JS文件,这样就可以打包后进行修改JS文件,让配置实时生效。

但是配置文件会很庞大,不适合放在 .env 文件中,所以该方案放弃。

Vite 针对静态资源的处理,提供了 public 指定目录。可以将资源放在指定的 public 目录中,它应位于你的项目根目录。该目录中的资源在开发时能直接通过  / 根路径访问到,并且打包时会被完整复制到目标目录的根目录下。

请注意:

  • public
    public/icon.png
    /icon.png
    
  • public 中的资源不应该被 JavaScript 文件引用。

尝试使用 import 语法引入到JS文件中, Vite 会报错。提示你需要将资源使用 script 或者 link 的方式在 html 文件里引入。

public 目录可以看作是 webpack 下的 static 目录,会完整的将整个目录复制到最终的打包文件中。那么可以将配置文件放入到 public 目录下。打包后可以修改配置文件里的值,并且确保浏览器不会对该文件进行缓存后,刷新浏览器便可以得到最新的替换文本。这里我采用了使用随机数的方式来让浏览器不缓存文件。

具体做法是在 index.html 中写入:

<script>
(() => {
var script = document.createElement("script");
script.src = "/resource/options/myjs.js?randomId=" + new Date().getTime();
document.body.appendChild(script);
})();
</script>

然后 myjs.js 文件里将配置对象赋值给 window 全局对象自定义属性 __DynamicTextOptions__ ,尽量确保不会与现有属性冲突,并且不会被覆盖。

为了确保属性不被意外改写,这里还做了一些额外的处理。

window.__DynamicTextOptions__ = {}; // some options
Object.freeze(window.__DynamicTextOptions__);
Object.defineProperty('window', '__DynamicTextOptions__', { configurable: false, writable: false })

然后在业务代码中,直接获取 window.__DynamicTextOptions__ 上的对象内容即可。

至此,就实现了可以根据配置文件动态替换文本的需求。

总结

本文是由项目上遇到的一个小问题而诞生。探索了如何不需要重新打包,只修改打包后暴露的配置文件,进而替换页面上的文字。

总结了一下浏览器的强缓存和协商缓存。

  • 与强缓存相关的头部包括 Cache-control 和 Expries 。
  • 与协商缓存相关的头部包括 Last-Modify/If-Modify-Since 和 ETag/If-None-Match 。

也总结了如何让浏览器不缓存文件,方式包括:

  • Cache-control: no-store
  • 静态资源文件增加版本号

  • 静态资源文件增加随机数

  • 使用 meta 标签禁用缓存

最终使用了静态资源文件后面拼接时间戳的方式来达到不缓存文件的目的。


链接: https://fly63.com/article/detial/11511

浏览器缓存_HTTP强缓存和协商缓存

浏览器缓存主要分为强强缓存(也称本地缓存)和协商缓存(也称弱缓存),强缓存是利用http头中的Expires和Cache-Control两个字段来控制的,用来表示资源的缓存时间。协商缓存就是由服务器来确定缓存资源是否可用.

angularjs 缓存详解

一个缓存就是一个组件,它可以透明地存储数据,以便未来可以更快地服务于请求。缓存能够服务的请求越多,整体系统性能就提升得越多。

浏览器缓存问题原理以及解决方案

浏览器缓存就是把一个已经请求过的Web资源(如html页面,图片,js,数据等)拷贝一份副本储存在浏览器中,为什么使用缓存:减少网络带宽消耗,降低服务器压力,减少网络延迟,加快页面打开速度

Web缓存相关知识整理

一个H5页面在APP端,如果勾选已读状态,则下次打开该链接,会跳过此页面。用到了HTML5 的本地存储 API 中的 localStorage作为解决方案,回顾了下Web缓存的知识

使用缓存加速之后的网站访问过程变化

在描述CDN的实现原理之前,让我们先看传统的未加缓存服务的访问过程,以便了解CDN缓存访问方式与未加缓存访问方式的差别,用户访问未使用CDN缓存网站的过程为:用户向浏览器提供要访问的域名;

html页面清除缓存

页面打开时,由于缓存的存在,刚刚更新的数据有时无法在页面得到刷新,当这个页面作为模式窗口被打开时问题更为明显, 如何将缓存清掉?

HTTP之缓存 Cache-Control

通过在Response Header设置Cache-Control head 信息可以控制浏览器的缓存行为。我们先来看一下Cache-Control可以设置哪些值:缓存头Cache-Control只能在服务端设置,在客户端是由浏览器设置的,自己不能修改它的值。

工程化_前端静态资源缓存策略

增量更新是目前大部分团队采用的缓存更新方案,能让用户在无感知的情况获取最新内容。具体实现方式通常是(一般我们通过构建工具来实现,比如webpack):

前端静态资源自动化处理版本号防缓存

浏览器会默认缓存网站的静态资源文件,如:js文件、css文件、图片等。缓存带来网站性能提升的同时也带来了一些困扰,最常见的问题就是不能及时更新静态资源,造成新版本发布时用户无法及时看到新版本的变化,严重影响了用户体验。

vue后台管理系统解决keep-alive页面路由参数变化时缓存问题

一个后台管理系统,一个列表页A路由配置需要缓存,另一个页面B里面有多个跳转到A路由的链接。问题描述:首先访问/A?id=1页面,然后到B页面再点击访问A?id=2的页面,发现由于页面A设置了缓存,数据还是id=1的数据,并没有更新。

点击更多...

内容以共享、参考、研究为目的,不存在任何商业目的。其版权属原作者所有,如有侵权或违规,请与小编联系!情况属实本人将予以删除!