Commit 3c3bfadb authored by “liyan”'s avatar “liyan”
parents 6ef78fe7 1775049b
Pipeline #19902 canceled with stage
### 前言
作为一个前端开发者,每天都在和浏览器打交道,对于浏览器缓存,相信都不会陌生,同时它也是我们日常开发中存在的一个非常重要的优化手段,无论在节省带宽、提高加载和渲染速度、减少网络阻塞,以及提高用户体验上,都有重要的作用。
### 为什么需要缓存?
- 减少用户等待时间,提升用户体验,直接从内存或磁盘中取缓存数据肯定是比从服务器请求更快的。
- 减少了冗余的数据传输。
- 减少服务器的负担,大大提升了网站性能。
### 缓存读写顺序
![在这里插入图片描述](https://img-blog.csdnimg.cn/4418790e27e34187b27f2dc5741b2b77.png#pic_center)
![在这里插入图片描述](https://img-blog.csdnimg.cn/089e4b196b3140a08ccf507205f24160.png#pic_center)
备注:
**memory cache** ==>浏览器本地缓存
**disk cache** => 硬盘缓存
1. 先在 浏览器缓存 中查找,如果有,直接加载。
2. 如果 浏览器缓存 中不存在,则在 硬盘 中查找,这里又细分:
1)如果有强缓存且未失效,则使用强缓存,不请求服务器。
2)如果有强缓存但已失效,使用协商缓存,比较后确定 304 还是 200;
3. 如果硬盘中也不存在,向服务器发起网络请求
4. 如果硬盘中也不存在,向服务器发起网络请求
***
下面将从** 缓存位置****缓存策略** 两个角度介绍浏览器缓存。
### 缓存位置
**memory cache(浏览器本地缓存)**
是浏览器内存中的缓存,相比于 disk cache 它的特点是读取速度快,但容量小,且时效性短;不受开发者控制,也不受HTTP协议头的约束。一旦浏览器 tab 页关闭,memory cache 就将被清空,再次重新打开相同页面时不再出现from memory cache的情况。
**disk cache(硬盘缓存)重点**
硬盘缓存取决于HTTP中的响应头信息,它也是浏览器缓存中最重要的内容。因为DNS缓存它主要是做一个ip地址查找并且是自主完成的,memory cache 也是不受控制,算是一个黑盒。所以剩下的可以受我们控制的硬盘缓存的重要性就不言而喻了,大多优化手段也是针对硬盘缓存。
根据 HTTP 响应头的各类字段进行判定资源的缓存规则,比如是否可以缓存,什么时候过期,过期之后是否需要重新发起请求呢?相比于 memory cache 的 disk cache 拥有存储空间时间长等优点。
### 缓存策略 - 强缓存和协商缓存
![外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传](https://img-home.csdnimg.cn/images/20230724024159.png?![在这里插入图片描述](https://img-blog.csdnimg.cn/899aaebbb3a946bf971b735c850a9c50.png#pic_center)
origin_url=7e63bfa1490c0013bbac3b012a832e50.png&pos_id=img-8561M81x-1699411348306)
根据 HTTP header 的字段将缓存分为两个部分,分别是**强缓存****协商缓存**
**强缓存:**使用强缓存策略时,如果缓存资源在过期时间内,是的话直接从本地缓存中读取资源,不与服务器进行通信。
**协商缓存:**如果强缓存失效后,客户端将向服务器发出请求,进行协商缓存。浏览器携带上一次请求返回的响应头中的 缓存标识 向服务器发起请求(如ETag、Last-Modified等),由服务器判断资源是否更新。如果资源没有更新,则返回状态码 304 Not Modified,告诉浏览器可以使用本地缓存;否则返回新的资源内容。强缓存优先级高于协商缓存,但是协商缓存可以更加灵活地控制缓存的有效性。
#### 强缓存
**强缓存**的字段有:**Expires **和 Cac**he-Control**。协商缓存的字段有:**Last**-**Modified ****ETag**
**Expires ****Cache-Control **都被设置的时候,浏览器会优先考虑后者。
**Last-Modified****ETag** 都被设置的时候,浏览器会优先考虑后者。
当强缓存失效的时候,则会进入到协商缓存阶段。
#### 协商缓存
一旦发现强缓存无效,浏览器会发送一个请求到服务器,服务器根据请求header中的部分信息来判断资源是否更新。如果没有更新,返回304重定向,告诉浏览器资源未更新,可继续使用本地的缓存;否则返回 状态码200 和 新的资源内容,浏览器缓存新的内容。
**Last-Modify / If-Modify-Since**
浏览器第一次请求一个资源的时候,服务器返回的 header 中会加上 Last-Modify,Last-Modify是一个时间标识该资源的最后修改时间。
当浏览器再次请求该资源时,请求头中会包含 If-Modify-Since,该值为缓存之前返回的 Last-Modify。服务器收到 If-Modify-Since 后,根据资源的最后修改时间判断资源是否更新。
资源未更新,返回**304**重定向,表示资源未更新可以继续使用缓存中的资源。
资源更新,返回200状态码,返回新的资源,并进行硬盘和浏览器缓存。
缺点:如果资源更新的速度是小于 1 秒的,那么该字段将失效,因为 Last-Modified 时间是精确到秒的。所以有了 ETag。
**ETag / If-None-Match**
与 Last-Modify/If-Modify-Since 不同的是,Etag/If-None-Match 返回的是一个校验码。ETag 可以保证每一个资源是唯一的,资源变化都会导致 ETag 变化。服务器根据浏览器上发送的 If-None-Match 值来判断是否缓存。
与 Last-Modified 不一样的是,当服务器返回 304 Not Modified 的响应时,由于 ETag 重新生成过,response header 中还会把这个 ETag 返回,即使这个 ETag 跟之前的没有变化。
<br/>
```javascript
Cache-Control: max-age=3600 // 我希望你把这个资源缓存起来,缓存时间是3600秒(1小时)
Expires: Mon Oct 17 2023 16:10:32 GMT // 到达指定时间过期
Date: Mon Oct 16 2023 13:30:30 GMT
Etag:W/"121-171ca289ebf"// (后面协商缓存内容)这个资源的编号是W/"121-171ca289ebf"
Last-Modified: Mon Oct 16 2023 09:20:10 GMT // (后面协商缓存内容)这个资源的上一次修改时间
```
现在越来越多的业务需要进行数据分析,数据查看等,然后关联的就要用到数据下载
***数据下载可以分为几种***
1、返回数据URL,前端直接跳转到URL进行下载;
2、返回数据列表,可以通过xlsx、xlsx-style等相关的插件,通过定制化表头,把列表数据依次遍历循环,然后生产文件;
3、返回数据流,前端经过数据解析和转换从而下载数据。
## 返回数据URL
这一种是最简单的,只要后端把数据处理好,然后生成现成的文件放在服务器上,我们只要通过a标签跳转过去就行了,或者点击按钮,window.open()打开相应的链接一样达到下载数据的目的。这种弊端就是,每次生成的文件如何不及时删除会占用服务器的内存,当然工作量这一块也要和后端沟通好,因为这种方式,大部分工作量都在后端这边。
## 返回数据列表
后端返回数据列表,我们可以插件[xlsx-style](https://gitee.com/amiko/js-xlsx)通过遍历循环把数据平铺下来,下载的表格,我们可以高度自定义,可以自己设定表头,表格的区域样式配置,处理sheet内容等等,然后调取插件封装好的方法下载下来。这种方法有个让我吐槽点,就是每次如果下载的数据要增加一列数据或者表头有修改,前后端就要进行动代码,感觉有点麻烦。具体的可以看一下[官方文档](https://gitee.com/amiko/js-xlsx)
## 返回数据流(强烈推荐)
第三种方法其实是我最推荐的,后端返回数据流,然后前端操作数据流从而下载数据。其实,第二种方法使用插件也是先把数据处理好,然后转为Blob数据流进行下载的。现在详细的介绍一下通过数据流下载的几种方式:
1. 使用window.open()方法
2. 使用Fetch API或XHR请求
3. 使用HTML5的download属性
4. 使用FileSaver.js库
5. 使用iframe
6. 使用FormData和XMLHttpRequest
7. 使用axios库
***1、使用window.open()方法***
在前端使用window.open()方法打开一个新的窗口或标签页,并将后端返回的文件流作为URL传递给该方法。浏览器会自动下载该文件(这里需要后端提前把文件处理好,不然下载的文件会有问题)。
```javascript
window.open('http://baidu.com/download', '_blank');
```
***2、使用Fetch API或XHR请求***
使用Fetch API或XHR(XMLHttpRequest)发送请求,获取后端返回的文件流,并使用Blob对象创建一个URL。然后将该URL传递给a标签的href属性,并使用JavaScript模拟点击该标签,触发文件下载。
```javascript
fetch('http://baidu.com/download')
.then(response => response.blob())
.then(blob => {
const url = URL.createObjectURL(blob);
const link = document.createElement('a');
link.href = url;
link.download = 'filename.ext';
link.click();
});
```
*这些方法可以根据具体的需求和项目环境选择使用。需要注意的是,后端返回的文件流需要正确设置Content-Disposition响应头,以指定文件的名称和下载方式。
除了上述提到的方法,还有以下五种方法可以实现前端导出和下载后端返回的文件流:*
***3、使用HTML5的download属性***
创建一个a标签,设置href属性为后端返回的文件流的URL,同时设置download属性为文件的名称。将该标签插入到DOM中,并使用JavaScript模拟点击该标签,触发文件下载。
```javascript
const link = document.createElement('a');
link.href = 'http://baidu.com/download';
link.download = 'filename.ext';
document.body.appendChild(link);
link.click();
document.body.removeChild(link);
```
***4、使用FileSaver.js库***
引入FileSaver.js库,使用saveAs()方法将后端返回的文件流保存为本地文件。需要将后端返回的文件流转换为Blob对象。
```javascript
import { saveAs } from 'file-saver';
fetch('http://baidu.com/download')
.then(response => response.blob())
.then(blob => {
saveAs(blob, 'filename.ext');
});
```
***5、使用iframe***
创建一个隐藏的iframe,将其src属性设置为后端返回的文件流的URL。浏览器会自动下载该文件。
```javascript
const iframe = document.createElement('iframe');
iframe.style.display = 'none';
iframe.src = 'http://baidu.com/download';
document.body.appendChild(iframe);
```
***6、使用FormData和XMLHttpRequest***
创建一个FormData对象,将后端返回的文件流作为Blob对象添加到FormData中。然后使用XMLHttpRequest发送请求,将FormData作为请求体发送到后端进行下载。
```javascript
const formData = new FormData();
formData.append('file', blob, 'filename.ext');
const xhr = new XMLHttpRequest();
xhr.open('POST', 'http://baidu.com/download');
xhr.send(formData);
```
***7、使用axios库***
使用axios库发送请求,获取后端返回的文件流,并将其保存为本地文件。需要将后端返回的文件流转换为Blob对象。
```javascript
import axios from 'axios';
axios.get('http://baidu.com/download', { responseType: 'blob' })
.then(response => {
const blob = new Blob([response.data]);
const url = URL.createObjectURL(blob);
const link = document.createElement('a');
link.href = url;
link.download = 'filename.ext';
link.click();
});
```
以上的这些方法提供了多种选择来实现前端导出和下载后端返回的数据。然后根据具体的需求和项目环境,选择适合的方法进行实现。
[参考文档](https://blog.csdn.net/ACCPluzhiqi/article/details/132514874?spm=1001.2100.3001.7377&utm_medium=distribute.pc_feed_blog_category.none-task-blog-classify_tag-4-132514874-null-null.nonecase&depth_1-utm_source=distribute.pc_feed_blog_category.none-task-blog-classify_tag-4-132514874-null-null.nonecase)
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment