Web页面中文件下载的几种方式
浏览器直接接管
这种方式通常通过创建一个a标签来,将需要下载的文件链接放到它的href属性上,设置download属性让浏览器将链接的 URL 视为下载资源。
js
const fileUrl = 'http://eg.com/file.zip';
// 创建一个隐藏的可下载链接
const downloadLink = document.createElement('a');
downloadLink.style.display = 'none';
downloadLink.download = 'file.zip';
// 不需要将它嵌入页面
downloadLink.click();
优点:
- 代码量少,简洁。
- 浏览器接管下载过程,随时可以暂停、继续。
缺点:
- 只支持
GET请求的链接。 - 带权限的链接需要添加权限验证信息到链接上。
- 图片等资源的链接需要在服务端接口设置该链接的
Content-Disposition为attachment,而非inline。不然会直接在浏览器上打开。
xhr或fetch获取文件
这种方式属于先调用接口,再保存。保存的方式分为创建URL对象使用浏览器下载器保存和调用现在浏览器接口直接保存
这种方式同样支持分片下载,不过不同的是无论是什么保存方式,都无法暂停后继续保存后续分片到当前文件中。
分片下载的方式有两种:
- 接口参数,例如下载链接为:
http://eg.com/file.zip?patch=1,通过传递参数告诉接口需要下载的是哪块分片,类似m3u8视频原理。不过在下载文件中不建议使用。 - 请求头Range,原理相同,方式不同,这种方式预先发起一个head请求,得到待下载文件的大小(Content-Length),然后循环发起分片请求并设置请求头
Range: bytes=${start}-${end},告诉服务器需要下载那部分内容,控制权在客户端。
创建URL对象保存
这种保存方式需要整个文件下载完成后才能保存,分布分片都无所谓了。
js
fetch(fileUrl)
.then(response => {
if (!response.ok) {
throw new Error(`HTTP error! Status: ${response.status}`);
}
return response.blob();
})
.then(blob => {
// 创建一个URL表示这个blob对象
const url = window.URL.createObjectURL(blob);
downloadLink.href = url;
downloadLink.download = 'file.zip';
downloadLink.click();
// 释放创建的URL对象
window.URL.revokeObjectURL(url);
})
.catch(error => {
console.error('下载文件时出错:', error);
});
优点:
- 可以使用项目封装的接口请求工具,不限制请求方式,不限制权限。
缺点:
- 需要将整个文件保存在内存中。
showSaveFilePicker
js
const fileHandle = await window.showSaveFilePicker({
suggestedName: 'file.zip'
});
const writableStream = await fileHandle.createWritable();
// 分片
const response = await fetch(fileUrl, {
headers: {
Range: `bytes=${start}-${end}`
}
});
if ((response.ok || response.status === 206) && response.body) {
const reader = response.body.getReader();
let done = false;
while (!done && !pauseRequested && !cancelRequested) {
const { value, done: chunkDone } = await reader.read();
if (value) {
await writableStream.write(value);
downloadedSize += value.length;
}
done = chunkDone;
}
}
优点:
- 不必将整个文件存在内存中,分片下载每片后,即可实时写入目标文件。
缺点:
-
浏览器兼容极差

![[微笑]](/face/0.gif)
![[嘻嘻]](/face/1.gif)
![[哈哈]](/face/2.gif)
![[可爱]](/face/3.gif)
![[可怜]](/face/4.gif)
![[挖鼻]](/face/5.gif)
![[吃惊]](/face/6.gif)
![[害羞]](/face/7.gif)
![[挤眼]](/face/8.gif)
![[闭嘴]](/face/9.gif)
![[鄙视]](/face/10.gif)
![[爱你]](/face/11.gif)
![[泪]](/face/12.gif)
![[偷笑]](/face/13.gif)
![[亲亲]](/face/14.gif)
![[生病]](/face/15.gif)
![[太开心]](/face/16.gif)
![[白眼]](/face/17.gif)
![[右哼哼]](/face/18.gif)
![[左哼哼]](/face/19.gif)
![[嘘]](/face/20.gif)
![[衰]](/face/21.gif)
![[委屈]](/face/22.gif)
![[吐]](/face/23.gif)
![[哈欠]](/face/24.gif)
![[抱抱]](/face/25.gif)
![[怒]](/face/26.gif)
![[疑问]](/face/27.gif)
![[馋嘴]](/face/28.gif)
![[拜拜]](/face/29.gif)
![[思考]](/face/30.gif)
![[汗]](/face/31.gif)
![[困]](/face/32.gif)
![[睡]](/face/33.gif)
![[钱]](/face/34.gif)
![[失望]](/face/35.gif)
![[酷]](/face/36.gif)
![[色]](/face/37.gif)
![[哼]](/face/38.gif)
![[鼓掌]](/face/39.gif)
![[晕]](/face/40.gif)
![[悲伤]](/face/41.gif)
![[抓狂]](/face/42.gif)
![[黑线]](/face/43.gif)
![[阴险]](/face/44.gif)
![[怒骂]](/face/45.gif)
![[互粉]](/face/46.gif)
![[心]](/face/47.gif)
![[伤心]](/face/48.gif)
![[猪头]](/face/49.gif)
![[熊猫]](/face/50.gif)
![[兔子]](/face/51.gif)
![[ok]](/face/52.gif)
![[耶]](/face/53.gif)
![[good]](/face/54.gif)
![[NO]](/face/55.gif)
![[赞]](/face/56.gif)
![[来]](/face/57.gif)
![[弱]](/face/58.gif)
![[草泥马]](/face/59.gif)
![[神马]](/face/60.gif)
![[囧]](/face/61.gif)
![[浮云]](/face/62.gif)
![[给力]](/face/63.gif)
![[围观]](/face/64.gif)
![[威武]](/face/65.gif)
![[奥特曼]](/face/66.gif)
![[礼物]](/face/67.gif)
![[钟]](/face/68.gif)
![[话筒]](/face/69.gif)
![[蜡烛]](/face/70.gif)
![[蛋糕]](/face/71.gif)