「本文已参与好文召集令活动,点击查看:后端、大前端双赛道投稿,2万元奖池等你挑战!」
最近很忙,一直没时间写文章,趁着今个儿心情大好,熬夜爆肝一篇,来聊聊下载那些事,随便下载个文件简单,那如何保证它的健壮呢,以及通用性呢。当然了这篇文章是基于下载的资源文件是比浏览器内的Blob对象所能支撑的最大数量小的前提下,并且浏览器内存够。
当然如果你想突破浏览器内在内存限制完成超大文件下载,可以查看这篇文章:前端自个突破浏览器Blob和RAM大小限制保存文件的骚玩法,当然我建议你先看完当前这篇,再扩展另一篇,循序渐进。
1. download
耳熟能详的资源文件下载,最先能想到的可能就是a标签了,借助html5的新增特性download。然后可能行云流水的写下如下通过代码:
/**
* @params url 下载资源路径
* @params name 文件名
*/
function saveAs(url,name){
var a = document.createElement('a');
a.download = name;
a.rel = 'noopener';
a.href = url;
a.click()
}
到这里你可能感觉很开心了,并且在同源环境下,能够正常的下载资源并且文件名称按照name的配置正常显示。
可是好景不长,你的同事沿用了你的方法,发现下载有问题了,且文件名咋不能按照入参name进行显示呢?一脸懵逼的找你看看,原来下载的资源是跨域资源!到这里就要让你同事了解下download的知识点了。
download属性指示浏览器下载 URL 而不是导航到它,因此将提示用户将其保存为本地文件。- 如果属性有一个值,那么此值将在下载保存过程中作为预填充的文件名。
- 此属性对允许的值没有限制,但是 / 和 \ 会被转换为下划线。
- 大多数文件系统限制了文件名中的标点符号,故此,浏览器将相应地调整建议的文件名。
- 此属性仅适用于同源URL。
同源就同源吧,跨域资源咱等会再换种方案。当你把这个方法当作基础工具库评审的时候,某大佬发问了a.click()能兼容所有浏览器吗?小心翼翼的熬夜翻看了JavaScript宝典,你终于明白了,小心翼翼做了如下更改:
function click(node){
try{
node.dispatchEvent(new MouseEvent('click'));
}catch(e){
var e = document.createEvent('MouseEvent');
e.initMouseEvent('click', true, true, window, 0, 0, 0, 80,20, false, false, false, false, 0, null);
node.dispatchEvent(evt);
}
}
大佬看了你如此写,大大的给你点赞,initMouseEvent参数为啥那么长?你怼回去:别问,问就是自己查宝典去!当然了,如果面对现代浏览器,其实是可以宽松些。
至此download下载资源文件就告一段段落,开始了下一段跨域资源下载之路。
2. Blob
Blob对象,先补充下基础知识。
- 它表示一个不可变、原始数据的类文件对象。它的数据可以按文本或二进制的格式进行读取,也可以转换成
ReadableStream来用于数据操作。 File接口基于Blob,继承了blob的功能并将其扩展使其支持用户系统上的文件。- 非
blob对象转Blob,请使用Blob()构造函数;创建子集blob,用slice()方法,大文件切片就靠它了。
了解到这里,又可以洋洋洒洒的写下代码了:
function download (url, name) {
var xhr = new XMLHttpRequest()
xhr.open('GET', url)
xhr.responseType = 'blob'
xhr.onload = function () {
var url = URL.createObjectURL(xhr.response)
saveAs(url, name)
setTimeout(function () { URL.revokeObjectURL(url) }, 4E4) // 40s
}
xhr.onerror = function () {
console.error('could not download file')
}
xhr.send()
}
兴高采烈的你又搞定了一件事情,但好久不长,你同事用你的代码去下载了不允许cors的资源,最后报错了,开始埋怨你代码这么不可靠。那就新增个是否支持跨域的判断吧~
function corsEnabled (url) {
var xhr = new XMLHttpRequest()
xhr.open('HEAD', url, false)
try {
xhr.send()
} catch (e) {}
return xhr.status >= 200 && xhr.status <= 299
}
注意了,这里请求是否允许cors是同步的。
corsEnabled (url)?download(url,name):saveAs(url,name)
3. msSaveOrOpenBlob
IE10来了,莫慌。它提供了msSaveBlob和msSaveOrOpenBlob两方法允许用户在客户端上保存文件,就像从Internet下载文件,这也是为啥此类文件能保存到下载文件夹。这两方法还是存在一些区别:
msSaveBlob只提供一个保存按钮msSaveOrOpenBlob提供保存和打开按钮
理论完了,直接上代码:
/**
*
* @params blob :Blob对象
* @params name :文件名
*/
function saveAs(blob,name,opts){
if('msSaveOrOpenBlob' in navigator){
navigator.msSaveOrOpenBlob(bom(blob, opts), name)
}
}
function bom (blob, opts) {
if (typeof opts === 'undefined') opts = { autoBom: false }
else if (typeof opts !== 'object') {
opts = { autoBom: !opts }
}
// 为UTF-8 XML和text/*类型(包括HTML)预先准备BOM表
// 浏览器会自动将UTF-16 U+FEFF转换为EF BB BF
if (opts.autoBom && /^\s*(?:text\/\S*|application\/xml|\S*\/\S*\+xml)\s*;.*charset\s*=\s*utf-8/i.test(blob.type)) {
return new Blob([String.fromCharCode(0xFEFF), blob], { type: blob.type })
}
return blob
}
完了吗,IE10都上了,能停停吗?还有FileReader的情况。
4. FileReader
还是老套路,先补充下理论。
- FileReader 对象允许Web应用程序异步读取存储在用户计算机上的文件(或原始数据缓冲区)的内容,使用 File 或 Blob 对象指定要读取的文件或数据。
- File对象来自input元素选择文件返回的FileList对象,也可以是拖拽生成的DataTransfer对象,或者是Canvas上执行mozGetAsFile()方法返回的结果.
- FileReader仅用于以安全的方式从用户(远程)系统读取文件内容 它不能用于从文件系统中按路径名简单地读取文件.
function saveAs(){
var isChromeIOS = /CriOS\/[\d]+/.test(navigator.userAgent);
var reader = new FileReader()
reader.onloadend = function () {
var url = reader.result
url = isChromeIOS ? url : url.replace(/^data:[^;]*;/, 'data:attachment/file;')
window.location.href = url
}
reader.readAsDataURL(blob)
}
到此为止,已经写了4种下载的方式,那这4种方式存在的情况下如何排优先级呢?
'download' in HTMLAnchorElement.prototype'msSaveOrOpenBlob' in navigatorFileReader
思考
下载就这些吗?
完了吗?
结束了吗?
到这就搞定了吗?
某天公司CEO在给投资人演示功能征求投资的时候,自信的使用你的下载功能下载一个10G的文件,完了完了完了,页面咋崩溃了。。。
注意:以上这些实现最大资源文件下载的大小依靠浏览器Blob对象的Max Size,在Chrome上是2GB,Firefox是800MiB,其他浏览器略有差异。
那为啥会崩溃呢?
通常发起一个URL请求到服务端,浏览器会根据返回的响应头Content-Type字段来区分返回的资源,如果返回的是application/octet-stream,显示数据是字节流,通常浏览器会按照下载类型来处理该请求,将它转发给下载管理器,由它执行IO操作去存储资源。这种情况下,浏览器基本不存在崩溃的情况。但是,如果将文件通过js直接读取到内存,一方面Blob存在大小限制,资源文件不能大于Blob Max Size,另一方面,大对象读到堆空间,可能撑爆空间,导致页面崩溃。
那如何解决呢?凌晨一点半了,该睡了,且听下回分解。
写得不对的地方,欢迎指正!