Skip to content

文件下载

浏览器里的下载并不只有一种方式。真正要先判断的是:文件是不是公开资源、是否需要鉴权、是否要读取响应头、文件体积是否很大。

常见下载方式

最常见的下载路径有 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、凭证和响应头暴露。

基于 MIT 许可发布