咱们知道,下载文件是一个很是常见的需求,但因为浏览器的安全策略的限制,咱们一般只能经过一个额外的页面,访问某个文件的 url 来实现下载功能,可是这种用户体验很是很差。
幸亏,HTML 5 里面为 <a>
标签添加了一个 download
的属性,咱们能够轻易的利用它来实现下载功能,不再须要用之前的笨办法了。javascript
咱们先看看 download
的使用方法:html
<a href="http://somehost/somefile.zip" download="filename.zip">Download file</a>
看看上面的代码,只要为 <a>
标签添加 download
属性,咱们点击这个连接的时候就会自动下载文件了~
顺便说下,download
的属性值是可选的,它用来指定下载文件的文件名。像上面的例子中,咱们下载到本地的文件名就会是 filename.zip 拉,若是不指定的话,它就会是 somefile.zip 这个名字拉!java
看到这里,你可能会说,坑爹啊,这明明是用 HTML 5 的新特性来实现下载文件嘛,说好的用 JavaScript 下载文件呢?git
事实上,用 JavaScript 来下载文件也是利用这一特性来实现的,咱们的 JavaScript 代码不外乎就是:github
用 JavaScript 建立一个隐藏的 <a>
标签数据库
设置它的 href
属性后端
设置它的 download
属性浏览器
用 JavaScript 来触发这个它的 click
事件安全
翻译成 JavaScript 代码就是:服务器
var a = document.createElement('a'); var url = window.URL.createObjectURL(blob); var filename = 'what-you-want.txt'; a.href = url; a.download = filename; a.click(); window.URL.revokeObjectURL(url);
好拉,是否是看到有个陌生的东东呢?
window.URL
里面有两个方法:
createObjectURL
用 blob 对象来建立一个 object URL(它是一个 DOMString
),咱们能够用这个 object URL 来表示某个 blob 对象,这个 object URL 能够用在 href
和 src
之类的属性上。
revokeObjectURL
释放由 createObjectURL
建立的 object URL,当该 object URL 不须要的时候,咱们要主动调用这个方法来获取最佳性能和内存使用。
知道了这两个方法以后,咱们再回去看看上面的例子就很容易理解了吧!只是用 blob 对象来建立一条 URL,而后让 <a>
标签引用该 URL,而后触发个点击事件,就能够下载文件了!
那么问题来了,blob 对象哪里来?
Blob 全称是 Binary large object,它表示一个类文件对象,能够用它来表示一个文件。根据 MDN 上面的说法,File API
也是基于 blob 来实现的。
因为本文的主题是讲 JavaScript 下载文件,那咱们构建 blob 的方式就是经过服务器返回的文件来建立 blob 拉!
而最简单的方式就是用 fetch API
了,咱们能够整合上面的例子:
fetch('http://somehost/somefile.zip').then(res => res.blob().then(blob => { var a = document.createElement('a'); var url = window.URL.createObjectURL(blob); var filename = 'myfile.zip'; a.href = url; a.download = filename; a.click(); window.URL.revokeObjectURL(url); }))
很简单对吧!
你可能会问,何须这么麻烦呢?直接写成下面这样不就行了:
<a href="http://somehost/somefile.zip" download="myfile.zip">Download file</a>
嗯,对于这种写法,我只能说,你作的太正确了!若是你要下载的是已经存在服务器上面的静态文件的话,那么写成这样是最方便的。浏览器会帮你处理整个下载过程,不须要你干涉。若是你用 blob 的方式来下载文件的话,会有下面这些限制的:
具体看看下面这个表格(出自 FileSaver.js):
Browser | Constructs as | Filenames | Max Blob Size | Dependencies |
---|---|---|---|---|
Firefox 20+ | Blob | Yes | 800 MiB | None |
Firefox < 20 | data: URI | No | n/a | Blob.js |
Chrome | Blob | Yes | 500 MiB | None |
Chrome for Android | Blob | Yes | 500 MiB | None |
Edge | Blob | Yes | ? | None |
IE 10+ | Blob | Yes | 600 MiB | None |
Opera 15+ | Blob | Yes | 500 MiB | None |
Opera < 15 | data: URI | No | n/a | Blob.js |
Safari 6.1+* | Blob | No | ? | None |
Safari < 6 | data: URI | No | n/a | Blob.js |
这一点限制对小文件(几十kb)可能没什么影响,但对稍微大一点的文件影响就很大了。试想,用户要下载一个 100mb 的文件,若是他点击了下载按钮以后没看到下载提示的话,他确定会继续按,等他按了几回以后还没看到下载提示时,他就会抱怨咱们的网站,而后离开了。
然而事实上下载的的确确发生了,只是要等到下载完文件以后才能构建 blob 对象,再转化成文件。并且,用户再触发多几回下载就会形成一些资源上的浪费。
所以,若是是要下载大文件的话,仍是推荐直接建立一个 <a>
标签拉~
写 html 也好,写 JavaScript 动态建立也好,用本身喜欢的方式去建立就行了。
好拉,说了半天,其实咱们一直说的都是:「不要用 JavaScript 下载文件拉,限制多多,又很差用,直接用 html 就好拉,简单方便又快捷」这个论调。
事实上也确实如此,但有些时候咱们确实须要经过 JavaScript 来作一些预处理。
有些时候,咱们须要对下载作一些限制,最多见的就是权限校验了,如检查该用户是否有下载的权限,是否有高速下载的权限等等。这时候,咱们能够利用 JavaScript 作一些预处理。如:
fetch('http://somehost/check-permission', options).then(res => { if (res.code === 0) { var a = document.createElement('a'); var url = res.data.url; var filename = 'myfile.zip'; a.href = url; a.download = filename; a.click(); } else { alert('You have no permission to download the file!'); } });
在这个例子里面,咱们没有用 blob 来构建 URL,而是经过后端服务器来计算出用户的下载连接,而后再利用以前提到的动态建立 <a>
标签的方式来实现下载,很简单吧!
动态生成文件而后返回给客户端也是一个很常见的需求,譬如咱们有时候须要作导出数据的功能,把数据库中的某些数据导出到 Excel 中,而后再返回客户端。
这时候咱们就不能简单的指定 href
属性,由于对应的 URL 并不存在。
咱们只能经过 JavaScript 对服务器发出一个请求,通知它去生成某个文件,而后把对应的 URL 返回给客户端。
有没有感受这个过程和上面「权限校验」一节很像?确定拉,由于咱们只是对 URL 作了一些预处理而已嘛~
因为 download
属性是 HTML 5 的新特性,所以它不支持旧版本的浏览器。
HTML 5 新的 download
特性真的很好用,结合 JavaScript 的动态能力咱们能够很方便的作出复杂的下载功能~
http://scarletsky.github.io/2016/07/03/download-file-using-javascript/
https://github.com/eligrey/FileSaver.js/blob/master/FileSaver.js
https://developer.mozilla.org/en-US/docs/Web/API/URL/createObjectURL
https://developer.mozilla.org/en-US/docs/Web/API/Blob
http://stackoverflow.com/questions/19327749/javascript-blob-filename-without-link
http://stackoverflow.com/questions/24501358/how-to-set-a-header-for-a-http-get-request-and-trigger-file-download
http://blog.bguiz.com/2014/07/03/file-download-with-http-request-header/