Javascript Blob

2023-02-17 10:56 更新

?arrayBuffer? 和視圖(view)都是 ECMA 標(biāo)準(zhǔn)的一部分,是 JavaScript 的一部分。

在瀏覽器中,還有其他更高級的對象,特別是 Blob,在 File API 中有相關(guān)描述。

Blob 由一個可選的字符串 type(通常是 MIME 類型)和 blobParts 組成 —— 一系列其他 Blob 對象,字符串和 BufferSource。


構(gòu)造函數(shù)的語法為:

new Blob(blobParts, options);
  • ?blobParts? 是 ?Blob?/?BufferSource?/?String? 類型的值的數(shù)組。
  • ?options? 可選對象:
    • ?type? —— ?Blob? 類型,通常是 MIME 類型,例如 ?image/png?,
    • ?endings? —— 是否轉(zhuǎn)換換行符,使 ?Blob? 對應(yīng)于當(dāng)前操作系統(tǒng)的換行符(?\r\n? 或 ?\n?)。默認(rèn)為 ?"transparent"?(啥也不做),不過也可以是 ?"native"?(轉(zhuǎn)換)。

例如:

// 從字符串創(chuàng)建 Blob
let blob = new Blob(["<html>…</html>"], {type: 'text/html'});
// 請注意:第一個參數(shù)必須是一個數(shù)組 [...]
// 從類型化數(shù)組(typed array)和字符串創(chuàng)建 Blob
let hello = new Uint8Array([72, 101, 108, 108, 111]); // 二進(jìn)制格式的 "hello"

let blob = new Blob([hello, ' ', 'world'], {type: 'text/plain'});

我們可以用 slice 方法來提取 Blob 片段:

blob.slice([byteStart], [byteEnd], [contentType]);
  • ?byteStart? —— 起始字節(jié),默認(rèn)為 0。
  • ?byteEnd? —— 最后一個字節(jié)(不包括,默認(rèn)為最后)。
  • ?contentType? —— 新 blob 的 ?type?,默認(rèn)與源 blob 相同。

參數(shù)值類似于 array.slice,也允許是負(fù)數(shù)。

?Blob? 對象是不可改變的

我們無法直接在 Blob 中更改數(shù)據(jù),但我們可以通過 slice 獲得 Blob 的多個部分,從這些部分創(chuàng)建新的 Blob 對象,將它們組成新的 Blob,等。

這種行為類似于 JavaScript 字符串:我們無法更改字符串中的字符,但可以生成一個新的改動過的字符串。

Blob 用作 URL

Blob 可以很容易用作 <a>、<img> 或其他標(biāo)簽的 URL,來顯示它們的內(nèi)容。

多虧了 type,讓我們也可以下載/上傳 Blob 對象,而在網(wǎng)絡(luò)請求中,type 自然地變成了 Content-Type。

讓我們從一個簡單的例子開始。通過點擊鏈接,你可以下載一個具有動態(tài)生成的內(nèi)容為 hello world 的 Blob 的文件:

<!-- download 特性(attribute)強(qiáng)制瀏覽器下載而不是導(dǎo)航 -->
<a download="hello.txt" href='#' id="link">Download</a>

<script>
let blob = new Blob(["Hello, world!"], {type: 'text/plain'});

link.href = URL.createObjectURL(blob);
</script>

我們也可以在 Javascript 中動態(tài)創(chuàng)建一個鏈接,通過 link.click() 模擬一個點擊,然后便自動下載了。

下面是類似的代碼,此代碼可以讓用戶無需任何 HTML 即可下載動態(tài)生成的 Blob(譯注:也就是通過代碼模擬用戶點擊,從而自動下載):

let link = document.createElement('a');
link.download = 'hello.txt';

let blob = new Blob(['Hello, world!'], {type: 'text/plain'});

link.href = URL.createObjectURL(blob);

link.click();

URL.revokeObjectURL(link.href);

URL.createObjectURL 取一個 Blob,并為其創(chuàng)建一個唯一的 URL,形式為 blob:<origin>/<uuid>

也就是 link.href 的值的樣子:

blob:https://javascript.info/1e67e00e-860d-40a5-89ae-6ab0cbee6273

瀏覽器內(nèi)部為每個通過 URL.createObjectURL 生成的 URL 存儲了一個 URL → Blob 映射。因此,此類 URL 很短,但可以訪問 Blob。

生成的 URL(即其鏈接)僅在當(dāng)前文檔打開的狀態(tài)下才有效。它允許引用 <img>、<a> 中的 Blob,以及基本上任何其他期望 URL 的對象。

不過它有個副作用。雖然這里有 Blob 的映射,但 Blob 本身只保存在內(nèi)存中的。瀏覽器無法釋放它。

在文檔退出時(unload),該映射會被自動清除,因此 Blob 也相應(yīng)被釋放了。但是,如果應(yīng)用程序壽命很長,那這個釋放就不會很快發(fā)生。

因此,如果我們創(chuàng)建一個 URL,那么即使我們不再需要該 Blob 了,它也會被掛在內(nèi)存中。

URL.revokeObjectURL(url) 從內(nèi)部映射中移除引用,因此允許 Blob 被刪除(如果沒有其他引用的話),并釋放內(nèi)存。

在上面最后一個示例中,我們打算僅使用一次 Blob,來進(jìn)行即時下載,因此我們立即調(diào)用 URL.revokeObjectURL(link.href)

而在前一個帶有可點擊的 HTML 鏈接的示例中,我們不調(diào)用 URL.revokeObjectURL(link.href),因為那樣會使 Blob URL 無效。在調(diào)用該方法后,由于映射被刪除了,因此該 URL 也就不再起作用了。

Blob 轉(zhuǎn)換為 base64

URL.createObjectURL 的一個替代方法是,將 Blob 轉(zhuǎn)換為 base64-編碼的字符串。

這種編碼將二進(jìn)制數(shù)據(jù)表示為一個由 0 到 64 的 ASCII 碼組成的字符串,非常安全且“可讀“。更重要的是 —— 我們可以在 “data-url” 中使用此編碼。

“data-url” 的形式為 data:[<mediatype>][;base64],<data>。我們可以在任何地方使用這種 url,和使用“常規(guī)” url 一樣。

例如,這是一個笑臉:

<img src="data:image/png;base64,R0lGODlhDAAMAKIFAF5LAP/zxAAAANyuAP/gaP///wAAAAAAACH5BAEAAAUALAAAAAAMAAwAAAMlWLPcGjDKFYi9lxKBOaGcF35DhWHamZUW0K4mAbiwWtuf0uxFAgA7" rel="external nofollow" >

瀏覽器將解碼該字符串,并顯示圖像:

我們使用內(nèi)建的 FileReader 對象來將 Blob 轉(zhuǎn)換為 base64。它可以將 Blob 中的數(shù)據(jù)讀取為多種格式。在下一章 我們將更深入地介紹它。

下面是下載 Blob 的示例,這次是通過 base-64:

let link = document.createElement('a');
link.download = 'hello.txt';

let blob = new Blob(['Hello, world!'], {type: 'text/plain'});

let reader = new FileReader();
reader.readAsDataURL(blob); // 將 Blob 轉(zhuǎn)換為 base64 并調(diào)用 onload

reader.onload = function() {
  link.href = reader.result; // data url
  link.click();
};

這兩種從 Blob 創(chuàng)建 URL 的方法都可以用。但通常 URL.createObjectURL(blob) 更簡單快捷。


Image 轉(zhuǎn)換為 blob

我們可以創(chuàng)建一個圖像(image)的、圖像的一部分、或者甚至創(chuàng)建一個頁面截圖的 Blob。這樣方便將其上傳至其他地方。

圖像操作是通過 <canvas> 元素來實現(xiàn)的:

  1. 使用 canvas.drawImage 在 canvas 上繪制圖像(或圖像的一部分)。
  2. 調(diào)用 canvas 方法 .toBlob(callback, format, quality) 創(chuàng)建一個 ?Blob?,并在創(chuàng)建完成后使用其運(yùn)行 ?callback?。

在下面這個示例中,圖像只是被復(fù)制了,不過我們可以在創(chuàng)建 blob 之前,從中裁剪圖像,或者在 canvas 上對其進(jìn)行轉(zhuǎn)換:

// 獲取任何圖像
let img = document.querySelector('img');

// 生成同尺寸的 <canvas>
let canvas = document.createElement('canvas');
canvas.width = img.clientWidth;
canvas.height = img.clientHeight;

let context = canvas.getContext('2d');

// 向其中復(fù)制圖像(此方法允許剪裁圖像)
context.drawImage(img, 0, 0);
// 我們 context.rotate(),并在 canvas 上做很多其他事情

// toBlob 是異步操作,結(jié)束后會調(diào)用 callback
canvas.toBlob(function(blob) {
  // blob 創(chuàng)建完成,下載它
  let link = document.createElement('a');
  link.download = 'example.png';

  link.href = URL.createObjectURL(blob);
  link.click();

  // 刪除內(nèi)部 blob 引用,這樣瀏覽器可以從內(nèi)存中將其清除
  URL.revokeObjectURL(link.href);
}, 'image/png');

如果我們更喜歡 async/await 而不是 callback:

let blob = await new Promise(resolve => canvasElem.toBlob(resolve, 'image/png'));

對于頁面截屏,我們可以使用諸如 https://github.com/niklasvh/html2canvas 之類的庫。它所做的只是掃一遍瀏覽器頁面,并將其繪制在 <canvas> 上。然后,我們就可以像上面一樣獲取一個它的 Blob。

Blob 轉(zhuǎn)換為 ArrayBuffer

Blob 構(gòu)造器允許從幾乎任何東西創(chuàng)建 blob,包括任何 BufferSource

但是,如果我們需要執(zhí)行低級別的處理時,我們可以從 blob.arrayBuffer() 中獲取最低級別的 ArrayBuffer

// 從 bolb 獲取 arrayBuffer
const bufferPromise = await blob.arrayBuffer();

// 或
blob.arrayBuffer().then(buffer => /* 處理 ArrayBuffer */);

Blob 轉(zhuǎn)換為 Stream

當(dāng)我們讀取和寫入超過 2 GB 的 blob 時,將其轉(zhuǎn)換為 arrayBuffer 的使用對我們來說會更加占用內(nèi)存。這種情況下,我們可以直接將 blob 轉(zhuǎn)換為 stream 進(jìn)行處理。

stream 是一種特殊的對象,我們可以從它那里逐部分地讀?。ɑ?qū)懭耄?。這塊的知識點不在本文的范圍之內(nèi),但這里有一個例子,你可以在 https://developer.mozilla.org/en-US/docs/Web/API/Streams_API 了解更多相關(guān)內(nèi)容。對于適合逐段處理的數(shù)據(jù),使用 stream 是很方便的。

Blob 接口里的 stream() 方法返回一個 ReadableStream,在被讀取時可以返回 Blob 中包含的數(shù)據(jù)。

如下所示:

// 從 blob 獲取可讀流(readableStream)
const readableStream = blob.stream();
const stream = readableStream.getReader();

while (true) {
  // 對于每次迭代:value 是下一個 blob 數(shù)據(jù)片段
  let { done, value } = await stream.read();
  if (done) {
    // 讀取完畢,stream 里已經(jīng)沒有數(shù)據(jù)了
    console.log('all blob processed.');
    break;
  }

  // 對剛從 blob 中讀取的數(shù)據(jù)片段做一些處理
  console.log(value);
}

總結(jié)

arrayBuffer,Uint8Array 及其他 BufferSource 是“二進(jìn)制數(shù)據(jù)”,而 Blob 則表示“具有類型的二進(jìn)制數(shù)據(jù)”。

這樣可以方便 Blob 用于在瀏覽器中非常常見的上傳/下載操作。

XMLHttpRequest,fetch 等進(jìn)行 Web 請求的方法可以自然地使用 Blob,也可以使用其他類型的二進(jìn)制數(shù)據(jù)。

我們可以輕松地在 Blob 和低級別的二進(jìn)制數(shù)據(jù)類型之間進(jìn)行轉(zhuǎn)換:

  • 我們可以使用 ?new Blob(...)? 構(gòu)造函數(shù)從一個類型化數(shù)組(typed array)創(chuàng)建 ?Blob?。
  • 我們可以使用 ?blob.arrayBuffer()? 從 ?Blob? 中取回 ?arrayBuffer?,然后在其上創(chuàng)建一個視圖(view),用于低級別的二進(jìn)制處理。

當(dāng)我們需要處理大型 blob 時,將其轉(zhuǎn)換為 stream 非常有用。你可以輕松地從 blob 創(chuàng)建 ReadableStreamBlob 接口的 stream() 方法返回一個 ReadableStream,其在被讀取時返回 blob 中包含的數(shù)據(jù)。


以上內(nèi)容是否對您有幫助:
在線筆記
App下載
App下載

掃描二維碼

下載編程獅App

公眾號
微信公眾號

編程獅公眾號