2022 年 5 月 16 日

Blob

ArrayBuffer 和檢視是 ECMA 標準的一部分,也是 JavaScript 的一部分。

在瀏覽器中,有額外的較高層級物件,在 檔案 API 中說明,特別是 Blob

Blob 包含一個選用的字串 type(通常是 MIME 類型),加上 blobParts – 其他 Blob 物件、字串和 BufferSource 的順序。

建構函式語法為

new Blob(blobParts, options);
  • blobPartsBlob/BufferSource/String 值的陣列。
  • options 選用的物件
    • typeBlob 類型,通常是 MIME 類型,例如 image/png
    • endings – 是否轉換行尾,讓 Blob 對應到目前作業系統的新行(\r\n\n)。預設為 "transparent"(不做任何事),但也可以是 "native"(轉換)。

例如

// create Blob from a string
let blob = new Blob(["<html>…</html>"], {type: 'text/html'});
// please note: the first argument must be an array [...]
// create Blob from a typed array and strings
let hello = new Uint8Array([72, 101, 108, 108, 111]); // "Hello" in binary form

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

我們可以使用 Blob 切片

blob.slice([byteStart], [byteEnd], [contentType]);
  • byteStart – 起始位元組,預設為 0。
  • byteEnd – 最後一個位元組(不包含,預設為最後一個)。
  • contentType – 新 blob 的 type,預設與來源相同。

引數類似於 array.slice,負數也是允許的。

Blob 物件是不可變的

我們無法直接在 Blob 中變更資料,但我們可以切片 Blob 的部分,從中建立新的 Blob 物件,將它們混合到新的 Blob 中,以此類推。

此行為類似於 JavaScript 字串:我們無法變更字串中的字元,但我們可以建立一個新的更正字串。

Blob 作為 URL

Blob 可以輕鬆用作 <a><img> 或其他標籤的 URL,以顯示其內容。

由於有 type,我們也可以下載/上傳 Blob 物件,而 type 自然會在網路要求中變成 Content-Type

讓我們從一個簡單的範例開始。透過按一下連結,您可以下載一個動態產生的 Blob,其內容為 hello world,作為一個檔案

<!-- download attribute forces the browser to download instead of navigating -->
<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 中動態建立一個連結,並透過 link.click() 模擬按一下,然後下載就會自動開始。

以下是類似的程式碼,會導致使用者下載動態建立的 Blob,而不需要任何 HTML

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,並為它建立一個唯一的 URL,格式為 blob:<origin>/<uuid>

這就是 link.href 的值

blob:https://javascriptinfo.dev.org.tw/1e67e00e-860d-40a5-89ae-6ab0cbee6273

對於每個由 URL.createObjectURL 產生的 URL,瀏覽器會在內部儲存一個 URL → Blob 的對應。因此,此類 URL 很短,但允許存取 Blob

產生的 URL(以及連結)僅在目前的文件開啟時有效。它允許在 <img><a> 中參照 Blob,基本上任何其他需要 URL 的物件都可以。

不過有一個副作用。當 Blob 有對應時,Blob 本身會駐留在記憶體中。瀏覽器無法釋放它。

文件卸載時會自動清除對應,因此 Blob 物件會在當時釋放。但如果應用程式執行時間很長,這就不會很快發生。

因此,如果我們建立一個 URL,即使不再需要,那個 Blob 仍會掛在記憶體中。

URL.revokeObjectURL(url) 會從內部對應中移除參考,因此允許刪除 Blob(如果沒有其他參考),並釋放記憶體。

在最後一個範例中,我們打算只使用 Blob 一次,用於立即下載,因此我們立即呼叫 URL.revokeObjectURL(link.href)

在前面那個有可按 HTML 連結的範例中,我們沒有呼叫 URL.revokeObjectURL(link.href),因為那會讓 Blob url 無效。在撤銷後,由於對應已移除,URL 就無法再運作了。

Blob 轉為 base64

URL.createObjectURL 的替代方案是將 Blob 轉換為 base64 編碼字串。

這種編碼將二進位資料表示為超安全「可讀取」字元的字串,其 ASCII 碼從 0 到 64。更重要的是,我們可以在「資料 URL」中使用這種編碼。

資料 URL 的格式為 data:[<mediatype>][;base64],<data>。我們可以在任何地方使用這種 URL,與「一般」URL 相同。

例如,這裡有一個笑臉

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

瀏覽器會解碼字串並顯示圖片:

要將 Blob 轉換為 base64,我們將使用內建的 FileReader 物件。它可以從多種格式的 Blob 中讀取資料。在 下一章 中,我們將更深入地探討它。

以下是透過 base-64 下載 blob 的示範

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); // converts the blob to base64 and calls onload

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

建立 Blob 的 URL 的這兩種方法都可以使用。但通常 URL.createObjectURL(blob) 比較簡單且快速。

URL.createObjectURL(blob)
  • 如果關心記憶體,我們需要撤銷它們。
  • 直接存取 blob,沒有「編碼/解碼」
Blob 轉為資料 URL
  • 不需要撤銷任何東西。
  • 編碼時,大型 Blob 物件會造成效能和記憶體損失。

圖片轉為 blob

我們可以建立圖片、圖片部分的 Blob,甚至製作網頁截圖。這對於將其上傳到某個地方很有用。

圖片操作是透過 <canvas> 元素完成的

  1. 使用 canvas.drawImage 在畫布上繪製影像(或其部分)。
  2. 呼叫畫布方法 .toBlob(callback, format, quality),它會建立一個 Blob,並在完成時執行 callback

在以下範例中,僅複製影像,但我們可以在建立 blob 之前,從中剪切或在畫布上轉換它。

// take any image
let img = document.querySelector('img');

// make <canvas> of the same size
let canvas = document.createElement('canvas');
canvas.width = img.clientWidth;
canvas.height = img.clientHeight;

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

// copy image to it (this method allows to cut image)
context.drawImage(img, 0, 0);
// we can context.rotate(), and do many other things on canvas

// toBlob is async operation, callback is called when done
canvas.toBlob(function(blob) {
  // blob ready, download it
  let link = document.createElement('a');
  link.download = 'example.png';

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

  // delete the internal blob reference, to let the browser clear memory from it
  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 到 ArrayBuffer

Blob 建構函式允許從幾乎任何東西建立 blob,包括任何 BufferSource

但是,如果我們需要執行低階處理,我們可以從 blob.arrayBuffer() 取得最低階的 ArrayBuffer

// get arrayBuffer from blob
const bufferPromise = await blob.arrayBuffer();

// or
blob.arrayBuffer().then(buffer => /* process the ArrayBuffer */);

從 Blob 到串流

當我們讀取和寫入超過 2 GB 的 blob 時,使用 arrayBuffer 對我們來說會更耗費記憶體。此時,我們可以直接將 blob 轉換為串流。

串流是一個特殊物件,允許逐部分讀取(或寫入)它。它不在我們的討論範圍內,但這裡有一個範例,你可以在 https://developer.mozilla.org/en-US/docs/Web/API/Streams_API 閱讀更多資訊。串流適用於適合逐部分處理的資料。

Blob 介面的 stream() 方法會傳回一個 ReadableStream,它在讀取時會傳回 Blob 中包含的資料。

然後,我們可以像這樣從中讀取

// get readableStream from blob
const readableStream = blob.stream();
const stream = readableStream.getReader();

while (true) {
  // for each iteration: value is the next blob fragment
  let { done, value } = await stream.read();
  if (done) {
    // no more data in the stream
    console.log('all blob processed.');
    break;
  }

   // do something with the data portion we've just read from the blob
  console.log(value);
}

摘要

雖然 ArrayBufferUint8Array 和其他 BufferSource 是「二進位資料」,但 Blob 代表「具有類型二進位資料」。

這使得 Blob 適用於瀏覽器中常見的上傳/下載作業。

執行網路要求的方法,例如 XMLHttpRequestfetch 等,可以原生支援 Blob,以及其他二進位類型。

我們可以輕鬆地在 Blob 和低階二進位資料類型之間進行轉換

  • 我們可以使用 new Blob(...) 建構函式,從型別化陣列建立 Blob
  • 我們可以使用 blob.arrayBuffer() 從 Blob 取得 ArrayBuffer,然後在它上面建立檢視,以進行低階二進位處理。

當我們需要處理大型 blob 時,轉換串流非常有用。你可以輕鬆地從 blob 建立 ReadableStreamBlob 介面的 stream() 方法會傳回一個 ReadableStream,它在讀取時會傳回 blob 中包含的資料。

教學課程地圖

留言

留言前請先閱讀…
  • 如果您有改進建議,請 提交 GitHub 議題 或發起拉取請求,而不是留言。
  • 如果您看不懂文章中的某個部分,請說明。
  • 要插入少量的程式碼,請使用 <code> 標籤;要插入多行程式碼,請將其包覆在 <pre> 標籤中;要插入超過 10 行的程式碼,請使用沙盒 (plnkrjsbincodepen…)