咱們知道,下載文件是一個很是常見的需求,但因爲瀏覽器的安全策略的限制,咱們一般只能經過一個額外的頁面,訪問某個文件的 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/