目录
前言
标签 download
a 标签链接下载的实现
1. 整体流程
2. 实现步骤
3. 类图
4. 代码示例
download 使用注意点
1. 同源 URL 的限制
2. 不能携带 Header
Blob 转换
方法1. 用作 URL(blob:)
方法2. 转换为 base64(data:)
两种方法总结与对比
responseType
在中后台项目中,前端难免需要处理下载的逻辑,需要下载的内容包括但不限于图片、Excel表格、CSV文件、MP4文件、PDF文件、TXT文件、JSON文件、HTML文件等等。虽然下载的内容各式各样,但是下载的原理大同小异。下面来一起学习一下前端是如何处理下载的。
这应该是最常见,最受广大人民群众喜闻乐见的一种下载方式了,搭配上 download 属性, 就能让浏览器将链接的 URL 视为下载资源,而不是导航到该资源。
如果 download 再指定个 filename ,那么就可以在下载文件时,将其作为预填充的文件名。不过名字中的 / 和 \ 会被转化为下划线 _,而且文件系统可能会阻止文件名中的一些字符,因此浏览器会在必要时适当调整文件名。
为了实现通过a标签链接下载文件,我们需要对a标签的href属性进行设置,使其指向文件的下载链接。具体步骤如下表所示:
下面我们将逐步介绍每个步骤需要做的事情,并提供相应的代码和注释。
步骤1:创建一个a标签元素
首先,我们需要使用document.createElement方法创建一个a标签元素,并将其赋值给一个变量,以便后续操作。
// 创建一个a标签元素
const downloadLink = document.createElement('a');
步骤2:设置a标签的href属性为文件的下载链接
接下来,我们需要将下载链接赋值给a标签的href属性,以便浏览器能够正确地下载文件。
// 设置a标签的href属性为下载链接
downloadLink.href = ''
步骤3:设置a标签的download属性,用于指定下载链接的默认文件名
如果我们想要指定下载链接的默认文件名,可以使用a标签的download属性。这样,当用户点击下载链接时,浏览器会自动将文件以指定的文件名保存到本地。
// 设置a标签的download属性为文件名
downloadLink.download = 'file.pdf'
步骤4:将a标签添加到HTML文档中
我们需要将创建的a标签元素添加到HTML文档中的某个元素中,以便用户能够看到下载链接并进行下载操作。
// 将a标签添加到HTML文档中的某个元素中
document.body.appendChild(downloadLink);
步骤5:触发a标签的点击事件,即模拟用户点击下载链接
为了触发文件的下载,我们需要模拟用户点击a标签的行为。可以使用click方法来触发a标签的点击事件。
// 触发a标签的点击事件,即模拟用户点击下载链接
downloadLink.click();
步骤6:完成文件下载
通过以上步骤,我们已经成功地实现了通过a标签链接下载文件的功能。用户点击下载链接后,浏览器会自动下载文件到本地。
下面是本文所涉及的类的关系示意图:
// 创建一个a标签元素
const downloadLink = document.createElement('a');
// 设置a标签的href属性为下载链接
downloadLink.href = '
// 设置a标签的download属性为文件名
downloadLink.download = 'file.pdf';
// 将a标签添加到HTML文档中的某个元素中
document.body.appendChild(downloadLink);
// 触发a标签的点击事件,即模拟用户点击下载链接
downloadLink.click();
download 只在同源 URL 或 blob: 、 data: 协议起作用
也就是说跨域是下载不了的......(这种说法不全对,除非后端配置 Content-Disposition 为 attachment,后面会讲)
首先,非同源 URL 会进行导航操作。其次,如果非要下载,可以先将其转换为 blob: 或 data: 再进行下载
使用 标签下载是带不了 Header 的,因此不能通过添加请求表头的形式来鉴权,但是可以将 sessionid 或 token 字段拼接到 URL 末尾来达到鉴权的目的。这里我们给出另一个解决方案:
这样是不是就能很好的同时解决问题1和问题2带来的两个痛点了呢,而且下载的文件名也能自定义了
顺便提一下,location.href 和 window.open 也存在同样的问题。
前文介绍到,在非同源请情况下可以将资源当成二进制的 blob 先拿到手,再进行 的下载处理。接下来,我们介绍两种 blob 的操作:
URL.createObjectURL 可以给 File 或 Blob 生成一个URL,形式为 blob:
// 下载 Excel 方法
excel(data, fileName) {
this.download0(data, fileName, "application/vnd.ms-excel");
},
// 下载 Word 方法
word(data, fileName) {
this.download0(data, fileName, "application/msword");
},
// 下载 Zip 方法
zip(data, fileName) {
this.download0(data, fileName, "application/zip");
},
// 下载 Html 方法
html(data, fileName) {
this.download0(data, fileName, "text/html");
},
// 下载 Markdown 方法
markdown(data, fileName) {
this.download0(data, fileName, "text/markdown");
},
download0(data, fileName, mineType) {
// 创建 blob
let blob = new Blob([data], { type: mineType });
// 创建 href 超链接,点击进行下载
window.URL = window.URL || window.webkitURL;
let href = URL.createObjectURL(blob);
let downA = document.createElement("a");
downA.href = href;
downA.download = fileName;
downA.click();
// 销毁超连接
window.URL.revokeObjectURL(href);
},
不过它有个副作用。虽然这里有 Blob 的映射,但 Blob 本身只保存在内存中的。浏览器无法释放它。
在文档退出时(unload),该映射会被自动清除,因此 Blob 也相应被释放了。但是,如果应用程序寿命很长,那这个释放就不会很快发生。
因此,如果我们创建一个 URL,那么即使我们不再需要该 Blob 了,它也会被挂在内存中。
不过,URL.revokeObjectURL 可以从内部映射中移除引用,允许 Blob 被删除并释放内存。所以,在即时下载完资源后,不要忘记立即调用 URL.revokeObjectURL。
作为 URL.createObjectURL 的一个替代方法,我们也可以将 Blob 转换为 base64-编码的字符串。这种编码将二进制数据表示为一个由 0 到 64 的 ASCII 码组成的字符串,非常安全且“可读”。
更重要的是 —— 我们可以在 “data-url” 中使用此编码。“data-url” 的形式为 data:[
FileReader 是一个对象,其唯一目的就是从 Blob 对象中读取数据,我们可以使用它的 readAsDataURL 方法将 Blob 读取为 base64。请看以下示例:
mport downloadByUrl from "@/utils/download";
const download = async () => {
const blob = await fetchFile();
// 声明一个 fileReader
const fileReader = new FileReader();
// 将 blob 读取成 base64
fileReader.readAsDataURL(blob);
// 读取成功后 下载资源
fileReader.onload = function () {
downloadByUrl(fileReader.result);
};
};
在上述例子中,我们先实例化了一个 fileReader,用它来读取 blob。
一旦读取完成,就可以从 fileReader 的 result 属性中拿到一个data: URL 格式的 Base64 字符串。
最后,我们给 fileReader 注册了一个 onload 事件,在读取操作完成后开始下载。
URL.createObjectURL(blob) 可以直接访问,无需“编码/解码”,但需要记得撤销(revoke);
而 Data URL 无需撤销(revoke)任何操作,但对大的 Blob 进行编码时,性能和内存会有损耗。
总而言之,这两种从 Blob 创建 URL 的方法都可以用。但通常 URL.createObjectURL(blob) 更简单快捷。
export const fetchFile = async (params) => {
return axios.get(api, {
params,
responseType: "blob"
});
};
最后,我们回头说一下请求的注意点:如果你的项目使用的是 XHR (比如 axios)而不是 fetch, 那么请记得在请求时添加上 responseType 为 'blob'。
responseType 不是 axios 中的属性,而是 XMLHttpRequest 中的属性,它用于指定响应中包含的数据类型,当为 "blob" 时,表明 Response 是一个包含二进制数据的 Blob 对象。
除了 blob 之外,responseType 还有 arraybuffer、json、text等其他枚举字符串值。
Blob 全称为 binary large object ,即二进制大对象,它是 JavaScript 中的一个对象,表示原始的类似文件的数据。下面是 MDN 中对 Blob 的解释:
Blob 对象表示一个不可变、原始数据的类文件对象。它的数据可以按文本或二进制的格式进行读取,也可以转换成 ReadableStream 来用于数据操作。
实际上,Blob 对象是包含有只读原始数据的类文件对象。简单来说,Blob 对象就是一个不可修改的二进制文件。
(1)Blob 创建
可以使用 Blob() 构造函数来创建一个 Blob:
new Blob(array, options);
其有两个参数:
array
:由 ArrayBuffer
、ArrayBufferView
、Blob
、DOMString
等对象构成的,将会被放进 Blob
;options
:可选的 BlobPropertyBag
字典,它可能会指定如下两个属性
type
:默认值为 "",表示将会被放入到 blob
中的数组内容的 MIME 类型。endings
:默认值为"transparent
",用于指定包含行结束符\n
的字符串如何被写入,不常用。常见的 MIME 类型如下:
下面来看一个简单的例子:
const blob = new Blob(["Hello World"], {type: "text/plain"});
这里可以成为动态文件创建,其正在创建一个类似文件的对象。这个 blob 对象上有两个属性:
size
:Blob对象中所包含数据的大小(字节);type
:字符串,认为该Blob对象所包含的 MIME 类型。如果类型未知,则为空字符串。下面来看打印结果:
const blob = new Blob(["Hello World"], {type: "text/plain"});
console.log(blob.size); // 11
console.log(blob.type); // "text/plain"
注意,字符串"Hello World"是 UTF-8 编码的,因此它的每个字符占用 1 个字节。
到现在,Blob 对象看起来似乎我们还是没有啥用。那该如何使用 Blob 对象呢?可以使用 URL.createObjectURL() 方法将将其转化为一个 URL,并在 Iframe 中加载:
const iframe = document.getElementsByTagName("iframe")[0];
const blob = new Blob(["Hello World"], {type: "text/plain"});
iframe.src = URL.createObjectURL(blob);
URL.createObjectURL()
静态方法会创建一个 DOMString,其中包含一个表示参数中给出的对象的 URL。这个 URL 的生命周期和创建它的窗口中的 document 绑定。这个新的 URL 对象表示指定的 File 对象或 Blob 对象。
备注: 此特性在 Web Worker 中可用
备注: 此特性在 Service Worker 中不可用,因为它有可能导致内存泄漏。
语法
objectURL = URL.createObjectURL(object);
参数 :object:用于创建 URL 的 File 对象、Blob 对象或者 MediaSource 对象。
返回值 :一个DOMString包含了一个对象 URL,该 URL 可用于指定源 object
的内容。
内存管理
在每次调用 createObjectURL()
方法时,都会创建一个新的 URL 对象,即使你已经用相同的对象作为参数创建过。当不再需要这些 URL 对象时,每个对象必须通过调用 URL.revokeObjectURL() 方法来释放。
浏览器在 document 卸载的时候,会自动释放它们,但是为了获得最佳性能和内存使用状况,你应该在安全的时机主动释放掉它们。