在日常开发中,我们经常需要实现文件下载功能。不同的业务场景可能需要不同的实现方式,这里整理了几种实用的方法,希望能帮助你解决实际问题。方式一:使用a标签的download属性
这是最简单直接的下载方式,适合静态文件或已知URL的情况:
<a href="/files/report.pdf" download>下载报告</a>
<a href="/files/data.xlsx" download="2024年销售数据.xlsx">下载Excel文件</a>
<button onclick="downloadStaticFile()">下载图片</button>
<script>function downloadStaticFile() { const link = document.createElement('a'); link.href = '/images/photo.jpg'; link.download = '我的照片.jpg'; link.click();}</script>
适用场景:
下载项目中的静态资源
下载同源服务器上的文件
需要简单快速实现下载功能
注意事项:
跨域资源可能无法正常下载
无法添加自定义请求头
部分浏览器对文件名有字符限制
方式二:通过页面跳转下载
这种方法通过改变页面地址或打开新窗口来触发下载:
function downloadByRedirect() { window.location.href = '/api/download/file/123';}
function downloadByNewWindow() { window.open('/api/export/pdf', '_blank');}
function downloadWithParams() { const params = new URLSearchParams({ startDate: '2024-01-01', endDate: '2024-12-31' });
window.location.href = `/api/report/export?${params}`;}
适用场景:
后端动态生成的文件
需要传递参数给后端
跨域下载(需服务器支持)
注意事项:
方式三:使用Fetch API和Blob对象
对于需要认证、进度显示或复杂处理的场景,这是最灵活的方式:
async function downloadWithFetch() { try { const response = await fetch('/api/files/download', { headers: { 'Authorization': 'Bearer ' + getToken() } });
const blob = await response.blob();
const url = window.URL.createObjectURL(blob); const link = document.createElement('a'); link.href = url;
const filename = getFilenameFromResponse(response) || 'download'; link.download = filename;
document.body.appendChild(link); link.click();
document.body.removeChild(link); window.URL.revokeObjectURL(url); } catch (error) { console.error('下载失败:', error); alert('文件下载失败,请重试'); }}
function getFilenameFromResponse(response) { const disposition = response.headers.get('Content-Disposition'); if (disposition && disposition.includes('filename=')) { let filename = disposition.split('filename=')[1]; filename = filename.replace(/['";]/g, ''); return decodeURIComponent(filename); } return null;}
function downloadWithProgress(url, onProgress) { return new Promise((resolve, reject) => { const xhr = new XMLHttpRequest(); xhr.open('GET', url, true); xhr.responseType = 'blob';
xhr.addEventListener('progress', (event) => { if (event.lengthComputable && onProgress) { const percent = Math.round((event.loaded / event.total) * 100); onProgress(percent); } });
xhr.onload = function() { if (xhr.status === 200) { const blob = xhr.response; const url = window.URL.createObjectURL(blob);
const disposition = xhr.getResponseHeader('Content-Disposition'); let filename = 'download'; if (disposition) { const match = disposition.match(/filename="?(.+)"?/); if (match) filename = match[1]; }
const link = document.createElement('a'); link.href = url; link.download = filename; document.body.appendChild(link); link.click(); document.body.removeChild(link); window.URL.revokeObjectURL(url);
resolve(); } else { reject(new Error(`下载失败: ${xhr.status}`)); } };
xhr.onerror = reject; xhr.send(); });}
downloadWithProgress('/api/large-file', (percent) => { console.log(`下载进度: ${percent}%`); });
方式四:下载前端生成的内容
有时候我们需要下载前端生成的数据,比如Canvas图片或JSON数据:
function downloadCanvasImage(canvasElement, filename = 'image.png') { canvasElement.toBlob((blob) => { const url = URL.createObjectURL(blob); const link = document.createElement('a'); link.href = url; link.download = filename; link.click(); URL.revokeObjectURL(url); });}
function downloadJSON(data, filename = 'data.json') { const jsonStr = JSON.stringify(data, null, 2); const blob = new Blob([jsonStr], { type: 'application/json' }); const url = URL.createObjectURL(blob); const link = document.createElement('a'); link.href = url; link.download = filename; link.click(); URL.revokeObjectURL(url);}
function downloadCSV(data, filename = 'data.csv') { let csvContent = '';
if (data.length > 0) { const headers = Object.keys(data[0]); csvContent += headers.join(',') + '\n'; }
data.forEach(row => { const values = Object.values(row).map(value => { if (typeof value === 'string' && (value.includes(',') || value.includes('"'))) { return `"${value.replace(/"/g, '""')}"`; } return value; }); csvContent += values.join(',') + '\n'; });
const blob = new Blob([csvContent], { type: 'text/csv;charset=utf-8;' }); const url = URL.createObjectURL(blob); const link = document.createElement('a'); link.href = url; link.download = filename; link.click(); URL.revokeObjectURL(url);}
实用技巧和注意事项
文件类型检测:
function getDownloadFilename(response, defaultName = 'file') { const disposition = response.headers.get('Content-Disposition'); let filename = defaultName;
if (disposition) { const matches = disposition.match(/filename\*?=["']?(?:UTF-8'')?([^"';]+)["']?/i); if (matches && matches[1]) { filename = decodeURIComponent(matches[1]); } }
return filename;}
错误处理和重试:
async function downloadWithRetry(url, maxRetries = 3) { for (let i = 0; i < maxRetries; i++) { try { await downloadWithFetch(url); return; } catch (error) { console.warn(`第${i + 1}次下载失败:`, error); if (i === maxRetries - 1) { throw error; } await new Promise(resolve => setTimeout(resolve, 1000 * (i + 1))); } }}
大文件分块下载:
对于非常大的文件,可以考虑使用流式下载或分块下载,避免内存占用过高。
总结
不同的下载方式适用于不同的场景:
选择哪种方式,主要看你的具体需求。如果只是下载一个静态图片,用最简单的a标签就好;如果需要下载需要认证的大文件,那么使用Fetch API配合进度显示会更合适。
在实际开发中,建议封装一个通用的下载函数,根据不同的参数选择合适的下载方式,这样既能保证代码复用,也能灵活应对各种需求。
阅读原文:原文链接
该文章在 2025/12/12 9:23:02 编辑过