主题
文件下载
浏览器里的下载并不只有一种方式。真正要先判断的是:文件是不是公开资源、是否需要鉴权、是否要读取响应头、文件体积是否很大。
常见下载方式
最常见的下载路径有 3 类:
- 直接使用超链接
- 让服务端通过响应头提示浏览器下载
- 先用
fetch拿到二进制数据,再转成Blob下载
什么时候直接用超链接
如果资源是公开可访问的静态文件,最简单的方式通常就是:
html
<a href="/files/report.pdf" download="report.pdf">下载文件</a>这种方式的优点是简单,浏览器会直接接管下载流程。
download 属性的作用
download 主要有两层含义:
- 提示浏览器以下载方式处理
- 提供默认文件名
但它不是绝对控制项,最终是否真的下载,还会受浏览器策略、跨域和服务端响应头影响。
服务端为什么经常决定“下不下载”
服务端可以通过 Content-Disposition 告诉浏览器更倾向于把响应当成附件下载:
http
Content-Type: application/pdf
Content-Disposition: attachment; filename="report.pdf"如果没有这个头,浏览器有时会直接预览,例如 PDF、图片、文本等资源。
什么时候用 fetch + blob
如果下载接口需要:
- 鉴权头
- 自定义请求参数
- 携带 Cookie
- 下载前先判断响应状态
这时通常更适合:
js
async function downloadFile() {
const response = await fetch('/api/export', {
headers: {
Authorization: `Bearer ${token}`,
},
})
if (!response.ok) {
throw new Error(`HTTP ${response.status}`)
}
const blob = await response.blob()
const url = URL.createObjectURL(blob)
const a = document.createElement('a')
a.href = url
a.download = 'report.xlsx'
a.click()
URL.revokeObjectURL(url)
}这类方式更适合前端主动控制流程。
为什么要关心 Blob URL
当前端拿到 Blob 后,通常不会直接“下载这个对象”,而是先把它转成临时地址:
js
const url = URL.createObjectURL(blob)下载结束后记得释放:
js
URL.revokeObjectURL(url)否则临时资源可能会一直占内存。
文件名从哪里来
如果文件名由服务端决定,常见做法是从 Content-Disposition 里解析:
js
const disposition = response.headers.get('content-disposition') || ''
const match = disposition.match(/filename="?([^"]+)"?/)
const filename = match?.[1] || 'download.bin'所以“文件下载”不只是拿到二进制,还经常涉及响应头读取。
为什么跨域下载经常出问题
常见原因包括:
- CORS 没放行
- 凭证没带上
- 响应头没有暴露给前端
如果前端要读取文件名,服务端通常还要暴露头信息:
http
Access-Control-Expose-Headers: Content-Disposition为什么下载后文件会损坏
高频原因有:
- 把二进制数据按 JSON 读了
- 把二进制当文本处理
- 服务端返回内容和声明类型不一致
例如文件下载场景里,误用 response.json() 就很容易出问题。
大文件下载怎么理解
如果文件很大,不一定适合先全部放进内存再交给前端处理。
通常优先顺序是:
- 普通大文件下载,优先让浏览器直接接管
- 需要进度或自定义处理时,再考虑流式方案
使用建议
- 公开资源优先用超链接或直接下载地址。
- 需要鉴权、自定义头或错误判断时,再用
fetch + blob。 - 需要保留服务端文件名时,重点看
Content-Disposition。 - 跨域下载问题不要只看前端代码,要同时看 CORS、凭证和响应头暴露。
