文件下载方式

前端涉及到的文件下载还是很多应用场景的,那么前端文件下载有多少种方式呢?每种方式有什么优缺点呢?下面就来一一介绍。

a标签

定义与用法

  • 首先实现download功能,条件必须满足:所要下载的文件与js或当前页面同源。即window.location.protocol(传输协议)+window.location.host(域名)必须有,且一致;
  • 如果是本地文件,请启动本地服务,使用localhost访问页面;
  • 通过a标签的download属性来实现文件下载;download为h5中新增的a标签属性;download+href使a标签具备点击下载功能;
  • download属性也可以设置一个值来规定下载文件的名称;若没有设置拓展名,浏览器将自动检测正确的拓展名(.img,.png、.pdf);

兼容性测试及结果

  • 代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<a href="./imgs/cs.jpg">同源图片,不带download</a>
</br>
<a href="./imgs/cs.jpg" download="cs">同源图片,带download</a>
</br>
<a href="https://www.erke.com/images/erker_logo.gif">网络图片,不带download</a>
</br>
<a href="https://www.erke.com/images/erker_logo.gif" download="hxek">网络图片,带download</a>
</br>
<a href="./cs.zip" >本地zip文件,不带download</a>
</br>
<a href="JavaScript:;" onclick="down('./videos/cs.zip')">js放置a标签实现(html无法打开的文件格式)</a>
</br>
<a href="JavaScript:;" onclick="down('./imgs/cs.jpg')">js放置a标签实现(html可以打开的文件格式)</a>
</br>
<a href="JavaScript:;" onclick="down('https://codeload.github.com/LingerCareer/Just-learn-JS-this/zip/refs/heads/main')">js放置a标签实现(html不能打开的网络文件格式)</a>
</br>
<a href="JavaScript:;" onclick="down('https://www.erke.com/images/erker_logo.gif')">js放置a标签实现(html可以打开的网络文件格式)</a>
1
2
3
4
5
6
function down(url) {
const a = document.createElement('a');
a.href = url;
a.download = '';
a.click();
}
  • pc端浏览器兼容性

download

总结

  • html不支持的文件,无论同源还是不同源,有没有download属性,都会下载。
  • html支持的文件,同源且有download属性,除ie外都会下载。
  • html支持的文件,不同源,无论有无download属性,都不会下载,浏览器会直接跳转打开。

Blob对象

场景:后端返回给前端文件流,前端通过Blob下载;文件流格式如下:

export

Blob对象

下面是blob对象的定义,来自MDN

Blob对象表示一个不可变、原始数据的类文件对象。它的数据可以按文本或二进制的格式进行读取,也可以转换成 ReadableStream 来用于数据操作。

Blob表示的不一定是JavaScript原生格式的数据。File 接口基于 Blob,继承了blob的功能并将其扩展以支持用户系统上的文件。

blob对象是html5新增的对象,它的作用是用来存储二进制数据的,比如图片、视频、音频等,它的使用方法如下:

1
2
3
4
5
6
7
/**
* @param {Array} array 二进制数据
* @param {Object} options 配置项
* @param {String} options.type 文件类型,它代表了将会被放入到 blob 中的数组内容的 MIME 类型。
* @param {String} options.endings 用于指定包含行结束符\n的字符串如何被写入。默认为transparent,表示不会修改行结束符。还可以指定为native,表示会将\n转换为\r\n。
*/
const blob = new Blob([], { type: '' })

这里主要关注的是type属性,默认情况下,blob对象是没有type属性的,那么这个Blob就是一个无类型的Blob,文件不会损毁,但是无法被正常识别。

URL.createObjectURL

下面是blob对象的定义,来自MDN

URL.createObjectURL() 静态方法会创建一个 DOMString,其中包含一个表示参数中给出的对象的 URL。这个 URL 的生命周期和创建它的窗口中的 document 绑定。这个新的 URL 对象表示指定的 File 对象或 Blob 对象。

这个方法是用来创建一个url的,它的作用是把一个blob对象转换成一个url,这个url可以用来下载文件,也可以用来预览文件,代码如下:

1
const url = URL.createObjectURL(blob)

这里需要注意的是,这个url的生命周期和创建它的窗口中的document绑定,也就是说,当我们的document被销毁后,这个url就会失效,会自动释放它们,但是为了获得最佳性能和内存使用状况,我们应该在安全的时机主动释放掉它们,代码如下:

1
URL.revokeObjectURL(url)

下载文件流

指定返回数据格式

首先指定返回数据格式为blob:responseType: 'blob'

1
2
3
4
5
6
7
8
9
// 导出报表
export function getMeetRoomExport(data) {
return request({
url: `xxxxxx`,
method: 'post',
responseType:"blob", //一定要加这个,不然下载的文件打不开
data
})
}

响应头headers获取文件信息

在响应数据response.headers["content-disposition"]中获取文件信息;包含文件名文件类型;如下图所示:

download

可以在响应拦截器里面做数据处理:

  • 中文转码了,转回去;
  • 将文件信息存到缓存,调用下载接口的时候获取文件信息;(给a标签的download设置文件名)
1
2
3
4
5
6
7
8
//接收到响应数据并成功后的一些共有的处理
if (response.headers["content-disposition"]) {
const temFileName =
response.headers["content-disposition"].split("filename=")[1];
const intoName = revertUTF8(temFileName.split("-")[0]); //decodeURI、decodeURIComponent都可以解决转码;
const fileName = intoName + "-" + temFileName.split("-")[1];
sessionStorage.setItem("fileName", fileName);
}

调用下载接口

  • 我们可以打印一下res,是一个Blob对象;(如果接口没有设置responseType: ‘blob’,这里是后端返回的文件流)
1
2
3
getMeetRoomExport(this.exportParams).then((res) => {
console.log(res);
});

download

  • 接口请求

这里和后端商量是docx类型的文件,所以type值如下所示,其他类型的type值设置可参考MDN

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
getMeetRoomExport(this.exportParams).then((res) => {
// console.log(res);
if (res) {
//这里和后端商量是docx类型的文件,所以type值如下:
let blob = new Blob([res], {
type: "application/vnd.openxmlformats-officedocument.wordprocessingml.document;charset=UTF-8",
});
let url = window.URL.createObjectURL(blob);
const link = document.createElement("a"); // 创建a标签
link.href = url;
link.download = sessionStorage.getItem("fileName"); // 重命名文件
link.click();
URL.revokeObjectURL(url); // 释放内存
}
});

至此,文件就会下载成功;

下载拓展

回到我们刚才下载的问题,我们是通过blob对象来解决,但是我们的type属性是写死的,如果在文件类型是确定的情况下是没问题的,但是如果这个接口就是下载文件的接口,文件可能是各种类型的,我们应该怎么处理?

  • 和接口提供者进行协商,确定下载的文件的格式,然后前端将type写死;
  • 通过枚举和响应头response.headers["content-disposition"]来实现;

枚举方式

  1. 新建config 写入blobType的对象
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
export const blobType = {
xls: 'application/vnd.ms-excel',
xlsx: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
csv: 'text/csv',
doc: 'application/msword',
docx: 'application/vnd.openxmlformats-officedocument.wordprocessingml.document',
pdf: 'application/pdf',
ppt: 'application/vnd.ms-powerpoint',
pptx: 'application/vnd.openxmlformats-officedocument.presentationml.presentation',
png: 'image/png',
gif: 'image/gif',
jpeg: 'image/jpeg',
jpg: 'image/jpeg',
mp3: 'audio/mpeg',
aac: 'audio/aac',
html: 'text/html',
css: 'text/css',
js: 'text/javascript',
json: 'application/json',
abw: 'application/x-abiword',
arc: 'application/x-freearc',
avi: 'video/x-msvideo',
azw: 'application/vnd.amazon.ebook',
bin: 'application/octet-stream',
bmp: 'image/bmp',
bz: 'application/x-bzip',
bz2: 'application/x-bzip2',
csh: 'application/x-csh',
eot: 'application/vnd.ms-fontobject',
epub: 'application/epub+zip',
htm: 'text/html',
ico: 'image/vnd.microsoft.icon',
ics: 'text/calendar',
jar: 'application/java-archive',
jsonld: 'application/ld+json',
mid: 'audio/midi audio/x-midi',
midi: 'audio/midi audio/x-midi',
mjs: 'text/javascript',
mpeg: 'video/mpeg',
mpkg: 'application/vnd.apple.installer+xml',
odp: 'application/vnd.oasis.opendocument.presentation',
ods: 'application/vnd.oasis.opendocument.spreadsheet',
odt: 'application/vnd.oasis.opendocument.text',
oga: 'audio/ogg',
ogv: 'video/ogg',
ogx: 'application/ogg',
otf: 'font/otf',
rar: 'application/x-rar-compressed',
rtf: 'application/rtf',
sh: 'application/x-sh',
svg: 'image/svg+xml',
swf: 'application/x-shockwave-flash',
tar: 'application/x-tar',
tif: 'image/tiff',
tiff: 'image/tiff',
ttf: 'font/ttf',
txt: 'text/plain',
vsd: 'application/vnd.visio',
wav: 'audio/wav',
weba: 'audio/webm',
webm: 'video/webm',
webp: 'image/webp',
woff: 'font/woff',
woff2: 'font/woff2',
xhtml: 'application/xhtml+xml',
xml: 'text/xml',
xul: 'application/vnd.mozilla.xul+xml',
zip: 'application/zip',
}
  1. 引入,通过缓存中的文件名后缀确定type值;大致思路如下:
1
2
3
4
5
6
7
8
9
// 1.引入枚举
import { blobType } from '@/config/global';
// 2.获取文件拓展名
let extName = sessionStorage.getItem("fileName").split(".")[1];
// console.log(extName); // docx
// 3.设置type值
let blob = new Blob([res], {
type: `${blobType[file_type]};charset=UTF-8`,
});

Blob拓展

不仅是后端传来的文件流可以下载,字符串、对象、数组等任意类型都可以下载;

1
2
3
4
5
6
7
8
9
10
11
12
13
// 下载txt格式的文件;
const btn = document.getElementById('btn');
let str = "使用Blob对象下载文件";
const blob = new Blob([str], { type: "text/plain" })
btn.onclick = () => {
// console.log(blob); // 和文件流一样,是个Blob对象
let url = window.URL.createObjectURL(blob);
const a = document.createElement("a");
a.href = url;
a.download = "fsl";
a.click();
window.URL.revokeObjectURL(url);
}

现象:点击按钮,下载名为 fsl.txt文件,内容为 “使用Blob对象下载文件”;

URL.createObjectURL拓展

使用URL.createObjectURL实现图片本地预览

这个方法是用来创建一个url的,它的作用是把一个blob对象转换成一个url,这个url可以用来下载文件,也可以用来预览文件;

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
<body>
<label for="submit">上传图片
<input type="file" id="submit">
</label>
<img src="" alt="pic" id="img">
</body>
<script>
const submit = document.getElementById('submit');
const img = document.getElementById('img');
img.style.display = "none";
submit.onchange = function (e) {
img.src = window.URL.createObjectURL(e.target.files[0]);
img.onload = function () {
img.style.display = "block";
URL.revokeObjectURL(e.target.files[0]);
}
}
</script>

参考文献

关于标签a的download属性兼容性总结

利用Blob实现前端导出Excel,Doc等文件

js根据文件后缀动态获取blob的type,并将流文件转blob

下载出来文件内容为空的解决方法

前端解析后端文件流并下载

js实现前端下载文件