AkiraZ's blog

愿键盘的余温传递到更遥远的将来

中文 / English
0%

JS 文件下载,Base64 与 Blob

Base64

之前写过一个方案,是通过转成 blob 再通过 FileReader 实现的,

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
function(base64) {
// 传进来的是一个 base64 编码的字符串
// 转成二进制
const byteString = atob(base64);
const u8Arr = new Uint8Array(byteString.split('').map(x => x.charCodeAt(0)));
// 生成二进制对象
const blob = new Blob([u8Arr]);
// 使用 reader 读取并下载
const reader = new FileReader();
reader.readAsDataURL(blob);
reader.onload = function(e) {
const a = document.createElement('a');
a.download = e.target.result;
document.body.appendChild(a);
a.click();
document.body.removeChild(a);
}
}

但是现在发现 Base64 可以直接下载

1
2
3
4
5
6
7
8
function(base64) {
const a = document.createElement('a');
// 直接把 base64 拼在href
a.download = 'data:application/octet-stream;base64' + base64;
document.body.appendChild(a);
a.click();
document.body.removeChild(a);
}

浏览器能直接识别出这种 href,以 data:application/octet-stream;base64 开头,表示默认需要下载的应用类型,并且可以直接进行 Base64 到二进制文件的转码。

关于 MIME 类型

MIME 类型 - HTTP | MDN (mozilla.org)

媒体类型,是一个指定文档、文件或字节流类型的名称标准。常见的有

  • text/plain
  • text/css
  • text/javascript
  • text/html
  • image/jpeg
  • application/octet-stream
  • multipart/form-data

其中,有的类型浏览器会按照文本读取,比如 html css javascript,而 application/octet-stream 类型浏览器会直接执行下载

附一个来自互联网号码分配机构(IANA)的媒体类型表 Media Types (iana.org)

Blob

FileReader 有一定的局限性,最近就遇到了这个问题,当文件小的时候还好说,但是如果文件大到一定体积,我这里尝试了 5M 左右,就会导致点击之后没有响应。

通过 debugger,可以看见浏览器中确实添加了一个巨大的标签,但是执行下载之后没有反应。

所以最后找到了一个对于 blob 的通用方法,base64 也可以先转码再使用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
function(base64) {
// 传进来的是一个 base64 编码的字符串
// 转成二进制
const byteString = atob(base64);
const u8Arr = new Uint8Array(byteString.split('').map(x => x.charCodeAt(0)));
// 生成二进制对象
const blob = new Blob([u8Arr]);
// 传入二进制对象 返回一个地址
const objectUrl = URL.createObjectURL(blob);
// href 参数直接指向下载地址
const a = document.createElement('a');
a.download = objectUrl;
document.body.appendChild(a);
a.click();
document.body.removeChild(a);
URL.revokeObjectURL(objectUrl);
}

通过查看浏览器,可以看到这个操作实际上是给这个二进制文件生成了一个临时的地址用来下载,所以挂载到这个页面上的体积其实很小,文件本体位于另一个页面。最后的下载就相当于下载了另一个 url 上的文件

创建完需要手动 URL.revokeObjectURL() 销毁,否则需要等到 dom 卸载才会销毁。