文件下载方式

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

a 标签

定义与用法

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

兼容性测试及结果

  • 代码
html
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>
js
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 新增的对象,它的作用是用来存储二进制数据的,比如图片、视频、音频等,它的使用方法如下:

js
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 可以用来下载文件,也可以用来预览文件,代码如下:

js
1
const url = URL.createObjectURL(blob)

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

js
1
URL.revokeObjectURL(url)

下载文件流

指定返回数据格式

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

js
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 设置文件名)
js
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’,这里是后端返回的文件流)
plaintext
1
2
3
getMeetRoomExport(this.exportParams).then((res) => {
console.log(res);
});

download

  • 接口请求

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

js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
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 = URL.createObjectURL(blob);
const link = document.createElement("a"); // 创建a标签
link.href = url;
link.download = sessionStorage.getItem("fileName"); // 重命名文件
link.style.display = "none"; // 将链接设置为不可见
document.body.appendChild(link);
link.click();
document.body.removeChild(link);
URL.revokeObjectURL(url); // 释放内存
}
});

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

下载拓展

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

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

枚举方式

  1. 新建 config 写入 blobType 的对象
js
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 值;大致思路如下:
js
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 拓展

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

js
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 可以用来下载文件,也可以用来预览文件;

html
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 实现前端下载文件