前端开发中,文件下载功能的应用场景十分广泛。那么,前端实现文件下载究竟有多少种方法?每种方法又有哪些优缺点和适用场景呢?本文将为你一一梳理和介绍。
1. a 标签下载
通过 a 标签的 download 属性是实现文件下载最简单、最常见的方式。先来看示例代码:
<a href="http://www.baidu.com" download="baidu.html">下载</a>
点击上面的示例链接,你会发现浏览器直接跳转到了百度的首页,并没有触发文件下载。这是因为 a 标签的 download 属性只能用于下载同源资源。对于跨域的链接(包括图片、音视频等媒体文件),浏览器会直接导航至该链接或进行预览,而不会下载。
上面的代码是直接书写 HTML 标签,我们也可以通过 JavaScript 动态创建来实现:
const a = document.createElement('a')
a.href = 'http://www.baidu.com'
a.download = 'baidu.html'
a.click()
效果和静态标签一样,同样是跳转而非下载。
这里的关键在于 a 标签的 download 属性,这是 HTML5 新增的特性。它的作用是指定下载文件的文件名。如果不指定,浏览器会尝试使用 HTTP 响应头中的 Content-Disposition 字段提供的文件名;如果该字段也不存在,则会默认使用 URL 路径的最后一部分作为文件名。这是一种基础的 HTML/CSS/JS 交互操作。
2. window.open
使用 a 标签的案例也可以通过 window.open 来实现,效果相同:
window.open('http://www.baidu.com', '_blank')
这里的 _blank 指定了在新的窗口或标签页中打开链接。如果不指定,则会在当前页面打开。
window.open 也支持类似 download 的行为,可以通过第三个参数指定:
window.open('http://www.baidu.com', '_blank', 'download=baidu.html')
但这种方式同样存在局限性。相比于 a 标签,它不能用于下载 .html、.htm、.xml、.xhtml 等会被浏览器直接解析并打开的文件类型。当然,它也无法下载跨域资源。
3. location.href
这种方式与 window.open(url) 的效果一致:
location.href = 'http://www.baidu.com'
它继承了 window.open 方法的所有缺陷,因此通常不推荐用于文件下载,仅作了解即可。
4. location 的其他属性
location 对象上其他能引起页面跳转的属性,如 location.assign、location.replace 等,理论上也能用于触发对某个 URL 的访问,但其本质与 location.href 赋值相同。
location.assign('http://www.baidu.com')
location.replace('http://www.baidu.com')
// location.reload 通常用于刷新页面,但传入参数时行为类似跳转
location.reload('http://www.baidu.com')
这些方法的缺点与上述一致,且各自有额外的特性(如 replace 不会在历史记录中留下痕迹),在此不再展开。
5. XMLHttpRequest / Fetch
这种方式即我们常说的通过 AJAX 请求下载文件,axios、fetch 等库的本质也与此相同。它为我们提供了更强的控制能力。示例代码如下:
const xhr = new XMLHttpRequest()
xhr.open('GET', 'http://www.baidu.com')
xhr.send()
xhr.onload = function () {
const blob = new Blob([xhr.response], { type: 'text/html' })
const a = document.createElement('a')
a.href = URL.createObjectURL(blob)
a.download = 'baidu.html'
a.click()
}
我们跳过 XMLHttpRequest 的基础知识,专注于文件下载部分。其核心逻辑是:请求成功后,获取响应数据 (response),将其转换为一个 Blob 对象,然后通过 URL.createObjectURL 为该 Blob 生成一个本地临时 URL,最后利用动态创建的 a 标签并指定 download 属性来触发下载。
这里涉及两个关键概念:Blob 对象和 URL.createObjectURL。
5.1 Blob
下面是来自 MDN 对 Blob 对象的定义:
Blob 对象表示一个不可变、原始数据的类文件对象。它的数据可以按文本或二进制的格式进行读取,也可以转换成 ReadableStream 来用于数据操作。
Blob 表示的不一定是 JavaScript 原生格式的数据。File 接口基于 Blob,继承了 blob 的功能并将其扩展以支持用户系统上的文件。
Blob 是 HTML5 引入的,用于表示二进制数据块,常用于处理文件内容。其构造函数如下:
/**
* @param {Array} array 二进制数据数组
* @param {Object} options 配置项
* @param {String} options.type 文件类型,即 MIME 类型。
* @param {String} options.endings 指定包含行结束符\n的字符串如何被写入。
*/
const blob = new Blob([], { type: '' })
其中 type 属性至关重要。如果未指定或指定不当,生成的 Blob 文件可能无法被系统正确识别。
5.2 URL.createObjectURL
同样来自 MDN 的解释:
URL.createObjectURL() 静态方法会创建一个 DOMString,其中包含一个表示参数中给出的对象的 URL。这个 URL 的生命周期和创建它的窗口中的 document 绑定。这个新的 URL 对象表示指定的 File 对象或 Blob 对象。
这个方法用于为一个 Blob 或 File 对象生成一个本地 URL,该 URL 可用于文件下载或预览:
const url = URL.createObjectURL(blob)
需要注意的是,这个 URL 的生命周期与创建它的 document 绑定。在不再需要时,应手动释放以优化内存:
URL.revokeObjectURL(url)
回到下载问题,上面的例子中我们将 Blob 的 type 写死为 ‘text/html’。如果已知文件类型,这没有问题。但如果一个下载接口可能返回任意类型的文件,我们该如何处理?
解决方案通常有两种:一是与后端协商约定;二是通过前端动态获取。我们可以从响应头中获取准确的 Content-Type:
const type = response.headers['content-type']
const blob = new Blob([response.data], { type })
然而,有时服务器返回的 Content-Type 可能是泛化的 application/octet-stream(二进制流)。这时,我们可以借助 file-type 这类库,通过读取文件内容的魔术数字(magic number)来准确判断文件类型:
import {fileTypeFromStream} from 'file-type';
const type = await fileTypeFromStream(response.body);
const blob = new Blob([response.data], { type })
file-type 库的使用可参考其官方文档。这种方案属于更精细的 前端框架/工程化 范畴。
6. 总结
纵览上述多种方案,你会发现其最终几乎都落到了利用 a 标签的 download 属性或浏览器行为来触发下载。无论是直接使用浏览器内置行为,还是通过 AJAX 获取数据后再转换,文件下载的最终执行者仍然是浏览器本身。理解各种方法的原理和限制,有助于我们在不同场景下选择最合适的实现方案。
希望这篇关于前端文件下载的详细解析能对你有所帮助。如果你有更多想法或实践经验,欢迎在 云栈社区 与我们交流讨论。