如何让浏览器不缓存文件
前言
最近在项目开发中遇到一个需求:项目打包后,可以根据修改配置文件,进而动态替换页面上的文本。由于项目基本不涉及到后端,因此不考虑通过接口来修改。这就需要前端项目打包后需要暴露一个配置文件,每次页面刷新时会获取到最新的配置,达到动态替换页面文本的目的。
本文重点总结下如何可以让浏览器不缓存静态资源,保证每次获取的都是最新的资源。
浏览器缓存
想知道如何不缓存文件,就需要先了解浏览器是怎么判断是否要缓存文件的。这里要引出一个概念,那就是浏览器缓存。
浏览器缓存(Brower Caching)是浏览器在本地磁盘对用户最近请求过的文档进行存储,当访问者再次访问同一页面时,浏览器就可以直接从本地磁盘加载文档。
「浏览器缓存的优点有:」
- 减少了冗余的数据传输,节省了网费
- 减少了服务器的负担,大大提升了网站的性能
- 加快了客户端加载网页的速度
浏览器缓存主要有两类:缓存协商和彻底缓存,也有称之为「协商缓存」和「强缓存」。
浏览器在第一次请求发生后,再次请求时:
- 浏览器会先获取该资源缓存的header信息,根据其中的
Expires
和Cache-control
判断是否命中强缓存,若命中则直接从缓存中获取资源,包括缓存的header信息,本次请求不会与服务器进行通信; - 如果没有命中强缓存,浏览器会发送请求到服务器,该请求会携带第一次请求返回的有关缓存的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-Control | Last-Modied/Etag |
---|---|---|
地址栏回车 | 有效 | 有效 |
页面链接跳转 | 有效 | 有效 |
新开窗口 | 有效 | 有效 |
前进回退 | 有效 | 有效 |
F5刷新 | 无效 | 有效 |
Ctrl+F5强制刷新 | 无效 | 无效 |
参考链接:https://zxpsuper.github.io/Demo/advanced_front_end/browser/cache.html[1]
不缓存
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
标签禁用缓存
最终使用了静态资源文件后面拼接时间戳的方式来达到不缓存文件的目的。
参考资料
[1]
https://zxpsuper.github.io/Demo/advanced_front_end/browser/cache.html: https://zxpsuper.github.io/Demo/advanced_front_end/browser/cache.html
相关文章
- LockHunter 替代Unlocker的文件解锁软件
- 文件路径超过系统限制_linux文件名长度限制
- 【说站】php文件怎么在浏览器运行
- Android Studio 简单生成so文件并调用「建议收藏」
- VB打开与保存txt文件的方法的案例分享
- Java通用的Excel文件生成工具类,支持生成文件和浏览器直接下载详解编程语言
- Linux下修改文件权限指南(linux更改文件的权限)
- 备份Oracle文件:一步搞定(备份oracle文件)
- ES文件浏览器下载 ES文件浏览器 v4.0.4.1 去广告优化版
- 禁用谷歌浏览器清理计算机功能避免占用硬件资源和被谷歌扫描文件
- 从Linux中快速拷贝文件:使用CP连接(linuxcp连接)
- Linux下轻松解压RAR文件(linux解压rar文件)
- 如何将文件传输至Linux操作系统(将文件传到linux)
- 使用Linux中的pscp命令安全高效地传输文件(linuxpscp)
- 2017黑客大预言:病毒传播无需文件,无人机可能成为炸弹
- 浏览器预览PHP文件时顶部出现空白影响布局分析原因及解决办法
- 下载文件个别浏览器文件名乱码解决办法
- FireFox浏览器使用Javascript上传大文件
- php使浏览器直接下载pdf文件的方法
- Node.js模拟浏览器文件上传示例
- php强制文件下载而非在浏览器打开的自定义函数分享
- ThinkPHP模板引擎之导入资源文件方法详解
- Mac下使用Eclipse编译C/C++文件出现launchfailed,binarynotfound解决方案