需求描述
对于后端返回的文件的base64格式的编码,我们前端想提供一个下载功能,把这个文件还原回来(下载下来)。
肯定存在三方库等其它方法,这里先记录利用原生js<a>标签实现的方法
实现思路
把<a>标签的href属性设置为base64编码,并设置download属性,触发点击事件即可完成浏览器下载。
// 完整的base64编码格式:真正描述文件内容的base64编码前面会有类似于 'data:application/pdf;base64,' 的字符串来描述文件MIME类型(媒体类型,即文件类型相关)
const base64 = 'data:application/pdf;base64,sdfjalkscnDSFNKJFdasf...'
// 借助<a>标签实现下载:a标签的href属性可以设置为base64编码,然后同时a标签拥有download属性,a标签点击后的行为就是让浏览器下载资源(download的属性值为下载后的文件名),而不是页面转跳
let a = document.createElement('a');
a.href = base64;
a.download = 'fileName.png'; // 如果为空,默认文件名为:下载.xxx(后缀名与base64MIME部分指定)
a.click();
a = null; // a标签下载作用用完了,解除对它的引用即释放内存
存在问题
也有一些说法的意思是,浏览器在下载之前,还需要对base64编码进行解码,最终变成二进制使用,所以可能降低下载效率,所以我们可以手动处理base64转成二进制。但这个问题的场景应该是<a>标签稳定存在的情况,并且会多次被用户点击。我们这里a标签只在js中存在,不需要
然后base64过长影响代码可读性等等,但我们都没有把<a>标签添加到文档流中...似乎这个问题在我们的场景下也不存在。
总而言之吧,没遇到什么问题,上面简简单单几行代码,完全解决了下载问题
网上的“优化”代码
我不懂这样写有什么具体的优点(大佬会的话希望能指点下),但是我还是把这种写法相关的知识以及思想学会了:
思想:把base64转为blob二进制对象,然后blob作为URL.createObjectURL函数的参数创建<a>标签的href属性,搭配download属性完成下载(相比于上面的方法更换了一种href属性的形式)
export const downloadFile = (base64: string, fileName: string) => {
const base64ToBlob = function (base64: string) { // base64编码格式:'data:image/jpeg;base64,/9j/4AAQSkZJRgAB...'
const MIMEAndCode = base64.split(';base64,'); // 分割完整的base64编码分别得到各个部分(MIME文件类型相关、纯编码)
const contentType = MIMEAndCode[0].split(':')[1]; // image/jpeg,用于构造Blob对象时指定文件类型type
// Blob对象的第一个构造参数是BufferArray对象或者BufferArrayView对象或者Blob
// BufferArray对象或者BufferArrayView对象的区别就是BufferArray就是纯二进制内容,不方便我们直接操作,BufferArrayView对象比如Uint8Array数组,就是把二进制内容变成方便我们操作的数据
// 把纯base64编码转为解码后的字符串
const rawCode = window.atob(MIMEAndCode[1]);
// 创建一个Uint8Array数组,长度为解码后字符的长度
const rawCodeLength = rawCode.length;
const uInt8Array = new Uint8Array(rawCodeLength);
// 遍历,把每个解码后的字符通过charCodeAt方法转为对应字符的Unicode编码(一种编码而已,值为0 - 65535 之间的整数)
for (let i = 0; i < rawCodeLength; i++) {
uInt8Array[i] = rawCode.charCodeAt(i);
}
// new Blob第一个参数为数组,数组里面是BufferArray或者BufferArrayView对象,第二个配置对象的type属性指定文件类型
return new Blob([uInt8Array], {
type: contentType
});
};
const blob = base64ToBlob(base64); // 拿到base64编码对应的blob对象传给URL.createObjectURL方法,构建一个href值
let a: HTMLAnchorElement | null = document.createElement('a');
a.href = URL.createObjectURL(blob);
a.download = '';
a.click();
a = null;
};
我的业务代码
export const downloadFile = (content: string, fileName: string) => {
let a: HTMLAnchorElement | null = document.createElement('a');
a.href = content;
a.download = `${fileName}.xxx`;
a.click();
a = null;
};
plus:文件相关理解
通过这次完成下载需求的经历,我还弄明白了文件本质上存放的就是数据——文件后缀名与文件本身存放的数据没有强行绑定关系,意思就是比如我有一个图片hhh.png,那么我完全可以修改文件名后缀为hhh.jrd,这样图片就没法用了,原因不是因为文件存放的内容改变了,而是我们的电脑是根据文件后缀名决定如何打开(执行)这个文件的,hhh.jrd与原来hhh.png存放着完全相同的文件内容,我们把文件名改回hhh.png电脑又会把这个文件当作一个图片来打开,自然就又能用了。
base64呢,本身就是一种二进制内容的编码格式,任何文件理论上来讲都可以编码成base64格式(不嫌大就行),对应的base64编码也是对一个文件的完整描述(包括前面的文件类型描述和后面的文件内容编码),所以理论上只要能提供文件的base64编码给<a>标签,浏览器就能进行下载。