JavaScript tips —— 關於下載與匯出的二三事

nanchenk發表於2018-06-15

前言

在專案中經常會遇到下載或匯出服務端資源的需求,一般分為2種做法
  1. 獲取檔案流,編碼後下載
  2. 獲取檔案的url,直接下載
本文主要探討第二種方法,在最後會提及檔案流的方法。


瀏覽器的安全策略

在介紹方法之前,我們需要知道瀏覽器的一些安全機制,防止惡意程式碼對使用者的破壞。
現代瀏覽器(ie8除外)檢測到非使用者直接操作產生的新視窗,一般會阻止,比如在ajax的回撥中開啟新的視窗,因為這些操作並不是在使用者點選的執行緒中,所以會攔截。


預開新標籤頁

做法

  1. 在非同步操作之前,先開啟一個新標籤頁
  2. 請求後端資源的地址
  3. 獲取url後去修改空白頁的url
const downloadTab = window.open('about:blank');

ajax.get('xxx').then(url => {
    // 使用後端資源的url進行下載
    downloadTab.location.href = href;
}).catch(err => {
    // 處理異常
    downloadTab.close();
})

複製程式碼

缺點

  1. 不管請求成功還是失敗都會有新頁面的閃爍出現
  2. 開啟的新頁面在什麼時候關閉是個問題,因為如果請求時間過長,使用者可能自己關閉新頁面,更不好處理的情況是頁面什麼時候觸發了下載,因為如果只是更改url就關閉視窗可能還沒有開始下載的操作。


生成iframe

做法

為了避免頁面閃爍與關閉時機的問題,可以在當前頁面使用動態建立iframe的方式,直接下載
ajax.get('xxx').then(href => {
    if (!href) {
      return;
    }

    if (!this.downIframe) {
      this.downIframe = document.createElement('iframe'); // 動態建立iframe
      this.downIframe.src = href; // iframe 中載入的頁面
      this.downIframe.id = 'downloadIframe'; // iframe 中載入的頁面
      this.downIframe.style.width = '1px';
      this.downIframe.style.height = '1px';
      this.downIframe.style.position = 'absolute';
      this.downIframe.style.left = '-100px';
      document.body.appendChild(this.downIframe); // 新增到當前窗體
    } else {
      this.downIframe.src = href; // iframe 中載入的頁面
    }
}).catch(err => {
    // 處理異常
})
複製程式碼

缺點

雖然可以優雅的下載檔案了,可是如果需要定製下載檔案的名稱就會令人頭疼了,我們需要時機去改變下載檔案的名稱,否則就會使服務端的檔名稱。


生成標籤

利用download屬性設定檔名,
JavaScript tips —— 關於下載與匯出的二三事
ajax.get('xxx').then(href => {
    if (!href) {
      return;
    }

    var a = document.createElement('a');
    var url = href;
    var filename = 'test.zip';
    a.href = url;
    a.download = filename; // 在沒有download屬性的情況,target="_blank",仍會阻止開啟
    a.click();
}).catch(err => {
    // 處理異常
})
複製程式碼
注意:
有些瀏覽器需要將a標籤嵌入頁面才可執行。


處理檔案流

最後大概講一下如果後端返回流怎麼處理。
function funDownload(content, filename) {
    // 建立隱藏的可下載連結
    var link = document.createElement('a');
    link.download = filename;
    link.style.display = 'none';

    // 字元內容轉變成blob地址
    var blob = new Blob([content]);
    //如果是excel格式
    //var blob = new Blob([content], {type: 'application/vnd.ms-excel'}),
    link.href = URL.createObjectURL(blob);

    // 觸發點選
    document.body.appendChild(link);
    link.click();
    // 然後移除
    document.body.removeChild(link);
};
複製程式碼
URL.createObjectURL() 靜態方法會建立一個 DOMString,其中包含一個表示引數中給出的物件的URL。這個 URL 的生命週期和建立它的視窗中的 document 繫結。這個新的URL 物件表示指定的 File 物件或 Blob 物件。


注意:
在每次呼叫 createObjectURL() 方法時,都會建立一個新的 URL 物件,即使你已經用相同的物件作為引數建立過。當不再需要這些 URL 物件時,每個物件必須通過呼叫 URL.revokeObjectURL() 方法來釋放。瀏覽器會在文件退出的時候自動釋放它們,但是為了獲得最佳效能和記憶體使用狀況,你應該在安全的時機主動釋放掉它們。


相關文章