柚子快報(bào)邀請(qǐng)碼778899分享:前端 a鏈接 如何實(shí)現(xiàn)下載功能
柚子快報(bào)邀請(qǐng)碼778899分享:前端 a鏈接 如何實(shí)現(xiàn)下載功能
目錄
前言
標(biāo)簽 download
a 標(biāo)簽鏈接下載的實(shí)現(xiàn)
1. 整體流程
2. 實(shí)現(xiàn)步驟
3. 類圖
4. 代碼示例?
download 使用注意點(diǎn)
1. 同源 URL 的限制
2. 不能攜帶 Header
Blob 轉(zhuǎn)換
方法1. 用作 URL(blob:)
方法2. 轉(zhuǎn)換為 base64(data:)
兩種方法總結(jié)與對(duì)比
responseType
擴(kuò)展閱讀
1. Blob
2. URL.createObjectURL()
3. URL.revokeObjectURL()
4. FileReader.readAsDataURL()
前言
在中后臺(tái)項(xiàng)目中,前端難免需要處理下載的邏輯,需要下載的內(nèi)容包括但不限于圖片、Excel表格、CSV文件、MP4文件、PDF文件、TXT文件、JSON文件、HTML文件等等。雖然下載的內(nèi)容各式各樣,但是下載的原理大同小異。下面來(lái)一起學(xué)習(xí)一下前端是如何處理下載的。
這應(yīng)該是最常見(jiàn),最受廣大人民群眾喜聞樂(lè)見(jiàn)的一種下載方式了,搭配上 download 屬性, 就能讓瀏覽器將鏈接的 URL 視為下載資源,而不是導(dǎo)航到該資源。
如果 download 再指定個(gè) filename ,那么就可以在下載文件時(shí),將其作為預(yù)填充的文件名。不過(guò)名字中的 / 和 \ 會(huì)被轉(zhuǎn)化為下劃線 _,而且文件系統(tǒng)可能會(huì)阻止文件名中的一些字符,因此瀏覽器會(huì)在必要時(shí)適當(dāng)調(diào)整文件名。
a 標(biāo)簽鏈接下載的實(shí)現(xiàn)
1. 整體流程
為了實(shí)現(xiàn)通過(guò)a標(biāo)簽鏈接下載文件,我們需要對(duì)a標(biāo)簽的href屬性進(jìn)行設(shè)置,使其指向文件的下載鏈接。具體步驟如下表所示:
下面我們將逐步介紹每個(gè)步驟需要做的事情,并提供相應(yīng)的代碼和注釋。
2. 實(shí)現(xiàn)步驟
步驟1:創(chuàng)建一個(gè)a標(biāo)簽元素 首先,我們需要使用document.createElement方法創(chuàng)建一個(gè)a標(biāo)簽元素,并將其賦值給一個(gè)變量,以便后續(xù)操作。
// 創(chuàng)建一個(gè)a標(biāo)簽元素
const downloadLink = document.createElement('a');
步驟2:設(shè)置a標(biāo)簽的href屬性為文件的下載鏈接 接下來(lái),我們需要將下載鏈接賦值給a標(biāo)簽的href屬性,以便瀏覽器能夠正確地下載文件。
// 設(shè)置a標(biāo)簽的href屬性為下載鏈接
downloadLink.href = ''
步驟3:設(shè)置a標(biāo)簽的download屬性,用于指定下載鏈接的默認(rèn)文件名
如果我們想要指定下載鏈接的默認(rèn)文件名,可以使用a標(biāo)簽的download屬性。這樣,當(dāng)用戶點(diǎn)擊下載鏈接時(shí),瀏覽器會(huì)自動(dòng)將文件以指定的文件名保存到本地。
// 設(shè)置a標(biāo)簽的download屬性為文件名
downloadLink.download = 'file.pdf'
步驟4:將a標(biāo)簽添加到HTML文檔中
我們需要將創(chuàng)建的a標(biāo)簽元素添加到HTML文檔中的某個(gè)元素中,以便用戶能夠看到下載鏈接并進(jìn)行下載操作。
// 將a標(biāo)簽添加到HTML文檔中的某個(gè)元素中
document.body.appendChild(downloadLink);
步驟5:觸發(fā)a標(biāo)簽的點(diǎn)擊事件,即模擬用戶點(diǎn)擊下載鏈接
為了觸發(fā)文件的下載,我們需要模擬用戶點(diǎn)擊a標(biāo)簽的行為??梢允褂胏lick方法來(lái)觸發(fā)a標(biāo)簽的點(diǎn)擊事件。
// 觸發(fā)a標(biāo)簽的點(diǎn)擊事件,即模擬用戶點(diǎn)擊下載鏈接
downloadLink.click();
步驟6:完成文件下載
通過(guò)以上步驟,我們已經(jīng)成功地實(shí)現(xiàn)了通過(guò)a標(biāo)簽鏈接下載文件的功能。用戶點(diǎn)擊下載鏈接后,瀏覽器會(huì)自動(dòng)下載文件到本地。
3. 類圖
下面是本文所涉及的類的關(guān)系示意圖:
4. 代碼示例?
// 創(chuàng)建一個(gè)a標(biāo)簽元素
const downloadLink = document.createElement('a');
// 設(shè)置a標(biāo)簽的href屬性為下載鏈接
downloadLink.href = '
// 設(shè)置a標(biāo)簽的download屬性為文件名
downloadLink.download = 'file.pdf';
// 將a標(biāo)簽添加到HTML文檔中的某個(gè)元素中
document.body.appendChild(downloadLink);
// 觸發(fā)a標(biāo)簽的點(diǎn)擊事件,即模擬用戶點(diǎn)擊下載鏈接
downloadLink.click();
download 使用注意點(diǎn)
1. 同源 URL 的限制
download 只在同源 URL 或 blob: 、 data: 協(xié)議起作用
也就是說(shuō)跨域是下載不了的......(這種說(shuō)法不全對(duì),除非后端配置 Content-Disposition 為 attachment,后面會(huì)講)
首先,非同源 URL 會(huì)進(jìn)行導(dǎo)航操作。其次,如果非要下載,可以先將其轉(zhuǎn)換為 blob: 或 data: 再進(jìn)行下載
2. 不能攜帶 Header
先發(fā)送請(qǐng)求獲取 blob 文件流,這樣就能在請(qǐng)求時(shí)進(jìn)行鑒權(quán);鑒權(quán)通過(guò)后再執(zhí)行下載操作。
這樣是不是就能很好的同時(shí)解決問(wèn)題1和問(wèn)題2帶來(lái)的兩個(gè)痛點(diǎn)了呢,而且下載的文件名也能自定義了
順便提一下,location.href 和 window.open 也存在同樣的問(wèn)題。
Blob 轉(zhuǎn)換
前文介紹到,在非同源請(qǐng)情況下可以將資源當(dāng)成二進(jìn)制的 blob 先拿到手,再進(jìn)行 的下載處理。接下來(lái),我們介紹兩種 blob 的操作:
方法1. 用作 URL(blob:)
URL.createObjectURL 可以給 File 或 Blob 生成一個(gè)URL,形式為 blob:
// 下載 Excel 方法
excel(data, fileName) {
this.download0(data, fileName, "application/vnd.ms-excel");
},
// 下載 Word 方法
word(data, fileName) {
this.download0(data, fileName, "application/msword");
},
// 下載 Zip 方法
zip(data, fileName) {
this.download0(data, fileName, "application/zip");
},
// 下載 Html 方法
html(data, fileName) {
this.download0(data, fileName, "text/html");
},
// 下載 Markdown 方法
markdown(data, fileName) {
this.download0(data, fileName, "text/markdown");
},
download0(data, fileName, mineType) {
// 創(chuàng)建 blob
let blob = new Blob([data], { type: mineType });
// 創(chuàng)建 href 超鏈接,點(diǎn)擊進(jìn)行下載
window.URL = window.URL || window.webkitURL;
let href = URL.createObjectURL(blob);
let downA = document.createElement("a");
downA.href = href;
downA.download = fileName;
downA.click();
// 銷毀超連接
window.URL.revokeObjectURL(href);
},
不過(guò)它有個(gè)副作用。雖然這里有 Blob 的映射,但 Blob 本身只保存在內(nèi)存中的。瀏覽器無(wú)法釋放它。
在文檔退出時(shí)(unload),該映射會(huì)被自動(dòng)清除,因此 Blob 也相應(yīng)被釋放了。但是,如果應(yīng)用程序壽命很長(zhǎng),那這個(gè)釋放就不會(huì)很快發(fā)生。
因此,如果我們創(chuàng)建一個(gè) URL,那么即使我們不再需要該 Blob 了,它也會(huì)被掛在內(nèi)存中。
不過(guò),URL.revokeObjectURL 可以從內(nèi)部映射中移除引用,允許 Blob 被刪除并釋放內(nèi)存。所以,在即時(shí)下載完資源后,不要忘記立即調(diào)用 URL.revokeObjectURL。
方法2. 轉(zhuǎn)換為 base64(data:)
作為 URL.createObjectURL 的一個(gè)替代方法,我們也可以將 Blob 轉(zhuǎn)換為 base64-編碼的字符串。這種編碼將二進(jìn)制數(shù)據(jù)表示為一個(gè)由 0 到 64 的 ASCII 碼組成的字符串,非常安全且“可讀”。
更重要的是 —— 我們可以在 “data-url” 中使用此編碼?!癲ata-url” 的形式為 data:[
FileReader 是一個(gè)對(duì)象,其唯一目的就是從 Blob 對(duì)象中讀取數(shù)據(jù),我們可以使用它的 readAsDataURL 方法將 Blob 讀取為 base64。請(qǐng)看以下示例:
let blob = new Blob([res.data]); // res.data是后臺(tái)返回的文件
let reader = new FileReader();
reader.readAsDataURL(blob);
reader.onload = (e) => {
// 轉(zhuǎn)換完成,創(chuàng)建一個(gè)a標(biāo)簽用于下載
let a = document.createElement('a');
a.download = fileName;
a.href = e.target.result;
document.body.appendChild(a);
a.click();
document.body.removeChild(a);
}
在上述例子中,我們先實(shí)例化了一個(gè) fileReader,用它來(lái)讀取 blob。
一旦讀取完成,就可以從 fileReader 的 result 屬性中拿到一個(gè)data: URL 格式的 Base64 字符串。
最后,我們給 fileReader 注冊(cè)了一個(gè) onload 事件,在讀取操作完成后開(kāi)始下載。
兩種方法總結(jié)與對(duì)比
URL.createObjectURL(blob) 可以直接訪問(wèn),無(wú)需“編碼/解碼”,但需要記得撤銷(revoke);
而 Data URL 無(wú)需撤銷(revoke)任何操作,但對(duì)大的 Blob 進(jìn)行編碼時(shí),性能和內(nèi)存會(huì)有損耗。
總而言之,這兩種從 Blob 創(chuàng)建 URL 的方法都可以用。但通常 URL.createObjectURL(blob) 更簡(jiǎn)單快捷。
responseType
export const fetchFile = async (params) => {
return axios.get(api, {
params,
responseType: "blob"
});
};
最后,我們回頭說(shuō)一下請(qǐng)求的注意點(diǎn):如果你的項(xiàng)目使用的是 XHR (比如 axios)而不是 fetch, 那么請(qǐng)記得在請(qǐng)求時(shí)添加上 responseType 為 'blob'。
responseType 不是 axios 中的屬性,而是 XMLHttpRequest 中的屬性,它用于指定響應(yīng)中包含的數(shù)據(jù)類型,當(dāng)為 "blob" 時(shí),表明 Response 是一個(gè)包含二進(jìn)制數(shù)據(jù)的 Blob 對(duì)象。
除了 blob 之外,responseType 還有 arraybuffer、json、text等其他枚舉字符串值。
擴(kuò)展閱讀
1. Blob
Blob 全稱為 binary large object ,即二進(jìn)制大對(duì)象,它是 JavaScript 中的一個(gè)對(duì)象,表示原始的類似文件的數(shù)據(jù)。下面是 MDN 中對(duì) Blob 的解釋:
Blob 對(duì)象表示一個(gè)不可變、原始數(shù)據(jù)的類文件對(duì)象。它的數(shù)據(jù)可以按文本或二進(jìn)制的格式進(jìn)行讀取,也可以轉(zhuǎn)換成?
ReadableStream?來(lái)用于數(shù)據(jù)操作。
實(shí)際上,Blob 對(duì)象是包含有只讀原始數(shù)據(jù)的類文件對(duì)象。簡(jiǎn)單來(lái)說(shuō),Blob 對(duì)象就是一個(gè)不可修改的二進(jìn)制文件。
(1)Blob 創(chuàng)建
可以使用 Blob() 構(gòu)造函數(shù)來(lái)創(chuàng)建一個(gè) Blob:
new Blob(array, options);
其有兩個(gè)參數(shù):
array:由?ArrayBuffer、ArrayBufferView、Blob、DOMString?等對(duì)象構(gòu)成的,將會(huì)被放進(jìn)?Blob;options:可選的?BlobPropertyBag?字典,它可能會(huì)指定如下兩個(gè)屬性
type:默認(rèn)值為 "",表示將會(huì)被放入到?blob?中的數(shù)組內(nèi)容的 MIME 類型。endings:默認(rèn)值為"transparent",用于指定包含行結(jié)束符\n的字符串如何被寫入,不常用。
常見(jiàn)的 MIME 類型如下:
下面來(lái)看一個(gè)簡(jiǎn)單的例子:
const blob = new Blob(["Hello World"], {type: "text/plain"});
?這里可以成為動(dòng)態(tài)文件創(chuàng)建,其正在創(chuàng)建一個(gè)類似文件的對(duì)象。這個(gè) blob 對(duì)象上有兩個(gè)屬性:
size:Blob對(duì)象中所包含數(shù)據(jù)的大?。ㄗ止?jié));type:字符串,認(rèn)為該Blob對(duì)象所包含的 MIME 類型。如果類型未知,則為空字符串。
下面來(lái)看打印結(jié)果:
const blob = new Blob(["Hello World"], {type: "text/plain"});
console.log(blob.size); // 11
console.log(blob.type); // "text/plain"
注意,字符串"Hello World"是 UTF-8 編碼的,因此它的每個(gè)字符占用 1 個(gè)字節(jié)。
到現(xiàn)在,Blob 對(duì)象看起來(lái)似乎我們還是沒(méi)有啥用。那該如何使用 Blob 對(duì)象呢?可以使用 URL.createObjectURL() 方法將將其轉(zhuǎn)化為一個(gè) URL,并在 Iframe 中加載:
const iframe = document.getElementsByTagName("iframe")[0];
const blob = new Blob(["Hello World"], {type: "text/plain"});
iframe.src = URL.createObjectURL(blob);
2. URL.createObjectURL()
URL.createObjectURL()?靜態(tài)方法會(huì)創(chuàng)建一個(gè)?DOMString,其中包含一個(gè)表示參數(shù)中給出的對(duì)象的 URL。這個(gè) URL 的生命周期和創(chuàng)建它的窗口中的?document?綁定。這個(gè)新的 URL 對(duì)象表示指定的?File?對(duì)象或?Blob?對(duì)象。
備注:?此特性在?Web Worker?中可用
備注:?此特性在?Service Worker?中不可用,因?yàn)樗锌赡軐?dǎo)致內(nèi)存泄漏。
語(yǔ)法
objectURL = URL.createObjectURL(object);
參數(shù)?:object:用于創(chuàng)建 URL 的?File?對(duì)象、Blob?對(duì)象或者?MediaSource?對(duì)象。
返回值?:一個(gè)DOMString包含了一個(gè)對(duì)象 URL,該 URL 可用于指定源?object的內(nèi)容。
內(nèi)存管理
在每次調(diào)用?createObjectURL()?方法時(shí),都會(huì)創(chuàng)建一個(gè)新的 URL 對(duì)象,即使你已經(jīng)用相同的對(duì)象作為參數(shù)創(chuàng)建過(guò)。當(dāng)不再需要這些 URL 對(duì)象時(shí),每個(gè)對(duì)象必須通過(guò)調(diào)用?URL.revokeObjectURL()?方法來(lái)釋放。
瀏覽器在 document 卸載的時(shí)候,會(huì)自動(dòng)釋放它們,但是為了獲得最佳性能和內(nèi)存使用狀況,你應(yīng)該在安全的時(shí)機(jī)主動(dòng)釋放掉它們。
3. URL.revokeObjectURL()
URL.revokeObjectURL()?靜態(tài)方法用來(lái)釋放一個(gè)之前已經(jīng)存在的、通過(guò)調(diào)用?URL.createObjectURL()?創(chuàng)建的 URL 對(duì)象。當(dāng)你結(jié)束使用某個(gè) URL 對(duì)象之后,應(yīng)該通過(guò)調(diào)用這個(gè)方法來(lái)讓瀏覽器知道不用在內(nèi)存中繼續(xù)保留對(duì)這個(gè)文件的引用了。
你可以在?sourceopen?被處理之后的任何時(shí)候調(diào)用?revokeObjectURL()。這是因?yàn)?createObjectURL()?僅僅意味著將一個(gè)媒體元素的?src?屬性關(guān)聯(lián)到一個(gè)?MediaSource?對(duì)象上去。調(diào)用revokeObjectURL()?使這個(gè)潛在的對(duì)象回到原來(lái)的地方,允許平臺(tái)在合適的時(shí)機(jī)進(jìn)行垃圾收集。
備注:?此特性在?Web Worker?中可用
語(yǔ)法:window.URL.revokeObjectURL(objectURL);
參數(shù): 一個(gè)?objo'b'jDOMString,表示通過(guò)調(diào)用?URL.createObjectURL()?方法產(chǎn)生的 URL 對(duì)象。
返回值 undefined
4. FileReader.readAsDataURL()
readAsDataURL?方法會(huì)讀取指定的?Blob?或?File?對(duì)象。讀取操作完成的時(shí)候,readyState?會(huì)變成已完成DONE,并觸發(fā)?loadend?事件,同時(shí)?result?屬性將包含一個(gè)data:URL 格式的字符串(base64 編碼)以表示所讀取文件的內(nèi)容
語(yǔ)法?
readAsDataURL(blob)
?參數(shù) blob即將被讀取的?Blob?或?File?對(duì)象。
例子
function previewFile() {
var preview = document.querySelector("img");
var file = document.querySelector("input[type=file]").files[0];
var reader = new FileReader();
reader.addEventListener(
"load",
function () {
preview.src = reader.result;
},
false,
);
if (file) {
reader.readAsDataURL(file);
}
}
柚子快報(bào)邀請(qǐng)碼778899分享:前端 a鏈接 如何實(shí)現(xiàn)下載功能
精彩文章
本文內(nèi)容根據(jù)網(wǎng)絡(luò)資料整理,出于傳遞更多信息之目的,不代表金鑰匙跨境贊同其觀點(diǎn)和立場(chǎng)。
轉(zhuǎn)載請(qǐng)注明,如有侵權(quán),聯(lián)系刪除。