File、Blob、ArrayBuffer等檔案類的物件有什麼區別和聯絡

山海之間發表於2020-10-18

前言

在前端中處理檔案時會經常遇到File、Blob、ArrayBuffer以及相關的處理方法或方式如FileReader、FormData等等這些名詞,對於這些常見而又不常見的名詞,我相信大多數人對它們都有一種熟悉的陌生人的感覺。究其原因,相關的東西接觸的不夠多,且每次都網上隨手拈來,不求甚解。今天,我們就稍微仔細一點,去做一個探究,弄清他們是誰,能做什麼,又有什麼區別,爭取下次再見既是“老朋友”。如果,你想更深入的瞭解相關知識點,可以參閱w3c和MDN的解釋,文後會附上相關的參考連結供參考。

內容

File

定義/概念

File即我們通常所說的檔案,我們硬碟裡儲存的音視訊、文件等等都是檔案。我們通常使用<input type="file">來選取並讀取本地計算機中的檔案,返回一個Filelist物件,此物件為一個類陣列可迭代物件。File物件是特殊型別的Blob,所以順便也繼承了Blob特有的方法和屬性,同時又有自己獨特的屬性和方法。

MDN定義:檔案(File)介面提供有關檔案的資訊,並允許網頁中的 JavaScript 訪問其內容。通常情況下, File 物件是來自使用者在一個 <input> 元素上選擇檔案後返回的 FileList 物件,也可以是來自由拖放操作生成的 DataTransfer 物件,或者來自 HTMLCanvasElement 上的 mozGetAsFile() API。

用法/示例

File常用的屬性有:

File.name 只讀,返回當前File 物件所引用檔案的名稱。

File.size 只讀,返回當前File 物件檔案的大小。

File.type 只讀,返回檔案的多用途網際網路郵件擴充套件型別(MIME Type)

更多屬性及方法資訊可參考MDN,這裡就不再詳細贅述。

FileList: <input type="file"> 元素有一個files屬性,用來儲存使用者所選擇的檔案,當使用者點選選擇檔案按鈕之後,便可以獲取到選擇的檔案組成的FileList物件。

1
2
const fileList = document.getElementById('file').files;
console.log(fileList);
 

結果

在這幾個當中,File應該是我們使用的頻率最高的一個,應該也是最熟悉的一個,所以過多的內容這裡就不一一示例。這裡引入一個很久之前遇到的一個相關的IE相容性問題。

input[type=file]這個檔案上傳原生按鈕不夠美觀,通常都是採取隱藏此原生的按鈕,使用另外一個自定義的按鈕,然後,通過點選此按鈕間接觸發隱藏的原生按鈕,從而實現這一功能。但是,由於IE安全限制,我們間接通過clik()觸發的,在IE9某些版本就會報SCRIPT: 拒絕訪問的錯誤。解決這個問題,要主動觸發上傳按鈕,此時藉助label的for屬性,繫結到對應的input上即可解決此問題。

Blob

定義/概念

Blob是Binary Large Object的縮寫,表示二進位制大物件,它並不是前端的所特有物件,而是計算機界的通用術語,在一些資料庫中,例如,MYSQL中的BLOB型別就表示二進位制資料的容器。MDN上對其的定義是:Blob 物件表示一個不可變、原始資料的類檔案物件。可以通俗的說,Blob就是一隻讀的二進位制物件。從File的介紹我們已知File繼承自Blob,有許多相同的方法和屬性,因此可以像操作File物件一樣操作Blob物件。

用法/示例

Blob主要包含兩個屬性

  • Blob.size:只讀,物件中所包含資料的大小(位元組)
  • Blob.type:只讀,一個字串,表明該 Blob 物件所包含資料的 MIME 型別。如果型別未知,則該值為空字串。(MIME型別參考

建立一個Blob物件,需要呼叫Blob建構函式。

1
2
3
4
5
/**
* @param {Array} array 一個由ArrayBuffer, ArrayBufferView, Blob, DOMString 等物件構成的陣列
* @param {Object} options 一個可選的BlobPropertyBag字典
*/
function Blob( array, options ){};
 

array 是一個由ArrayBufferArrayBufferViewBlobDOMString 等物件構成的 Array ,或者其他類似物件的混合體,它將會被放進 BlobDOMStrings會被編碼為UTF-8。

options 是一個可選的BlobPropertyBag字典,它可能會指定如下兩個屬性:

  • type,預設值為 "",它代表了將會被放入到blob中的陣列內容的MIME型別。
  • endings,預設值為"transparent",用於指定包含行結束符\n的字串如何被寫入。 它是以下兩個值中的一個: "native",代表行結束符會被更改為適合宿主作業系統檔案系統的換行符,或者 "transparent",代表會保持blob中儲存的結束符不變

使用示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
const data1 = "a";
const data2 = "b";
const data3 = "<div style='color:red;'>This is a blob</div>";
const data4 = { "name": "abc" };
// 建立blob物件
const blob1 = new Blob([data1]);
const blob2 = new Blob([data1, data2]);
const blob3 = new Blob([data3], {type : 'text/html'});
const blob4 = new Blob([JSON.stringify(data4)]);
const blob5 = new Blob([data4]);
const blob6 = new Blob([data3, data4]);

console.log(blob1); //輸出:Blob {size: 1, type: ""}
console.log(blob2); //輸出:Blob {size: 2, type: ""}
console.log(blob3); //輸出:Blob {size: 44, type: "text/html"}
console.log(blob4); //輸出:Blob {size: 14, type: ""}
console.log(blob5); //輸出:Blob {size: 15, type: ""}
console.log(blob6); //輸出:Blob {size: 59, type: ""}
 

以上blob5的size值列印為什麼是15呢?原因是,當使用普通物件建立Blob物件時,相當於呼叫了普通物件的toString()方法得到字串資料,然後再建立Blob物件。所以,blob5儲存的資料是"[object Object]",是15個位元組(不包含最外層的引號)。

Blob目前有四個方法:

Blob.slice([start[, end[, contentType]]]):返回一個新的 Blob 物件,包含了源 Blob 物件中指定範圍內的資料。(由於File繼承自Blob,可用此方法分割本地檔案,實現分片上傳)

Blob.stream():返回一個能讀取blob內容的 ReadableStream

Blob.text():返回一個promise且包含blob所有內容的UTF-8格式的 USVString

Blob.arrayBuffer():返回一個promise且包含blob所有內容的二進位制格式的 ArrayBuffer

ArrayBuffer

定義/概念

你從XHR、File API、Canvas等等各種地方,讀取了一大串位元組流,如果用JS裡的Array去存,又浪費,又低效。
於是為了配合這些新的API增強JS的二進位制處理能力,就有了ArrayBuffer。

ArrayBuffer簡單說就是一片記憶體,表示原始二進位制資料緩衝區。但不能直接操作它,而是要通過型別陣列物件TypedArray或 DataView (資料檢視)物件來操作它,它們會將緩衝區中的資料表示為特定的格式,並通過這些格式來讀寫緩衝區的內容。TypedArrayArrayBuffer提供了一個“View”,對它們進行下標讀寫。也可以使用DataView來讀寫ArrayBufferDataView能更自由的選擇位元組序,不用考慮不同平臺的位元組序問題。

MDN將ArrayBuffer 物件定義為用來表示通用的、固定長度的原始二進位制資料緩衝區。它是一個位元組陣列,通常在其他語言中稱為“byte array”。

用法示例

由於ArrayBuffer不能直接進行操作,故需要藉助TypedArray或者DataView來進行讀寫。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// 生成一個可以16個位元組的連續記憶體,每個位元組的預設值是0
const buffer = new ArrayBuffer(16);

// TypedArray 使用標準陣列語法來獲取和設定屬性值
var int16 = new Int16Array(2);
int16[0] = 42;
console.log(int16[0]); // 42

const buffer = new ArrayBuffer(16);
const a = new Uint8Array(buffer); // 建立Uint8Array檢視
const b = new Int32Array(buffer); // 建立Int32Array檢視
a[0] = 1;
b[0] = 2;
// 由於兩個檢視是對應的是同一段記憶體,所以其中一個檢視更改了記憶體,會影響到另一個檢視
a[0]; // 2
 

結語

區別/聯絡

File和Blob
  • 相同點: File和Blob都可以用來表示類檔案物件,處理檔案;

  • FIle: File可以看作一個承載檔案的橋樑,將DOM介面和檔案聯絡起來,通過File這個橋樑,獲取計算及內的檔案,從而對才能對檔案做進一步處理。

  • Blob:File繼承自Blob,他們之間很方便進行轉換,Blob是File都原型物件。

  • 聯絡:File繼承自Blob,同時又有自己獨特的屬性和方法。從下面的列印可以看出,其實Blob物件就是File的原型物件,自然就擁有了Blob物件的方法和屬性。

    1
    <input type="file" id="myfiles" />
     
    1
    2
    3
    4
    5
    6
    7
    const fileDOM = document.querySelector("#myfiles");
    const fileChange = (e) => {
    const files = fileDOM.files;
    console.log(files[0].__proto__) // 輸出File
    console.log(files[0].__proto__.__proto__) // 輸出Blob
    }
    fileDOM.onchange = fileChange;
     
Blob與ArrayBuffer
  • 相同點: BlobArrayBuffer都是二進位制的容器。

  • ArrayBuffer:ArrayBuffer更底層,是一段純粹的記憶體上的二進位制資料,我們可以對其任何一個位元組進行單獨的修改,也可以根據我們的需要以我們指定的形式讀取指定範圍的資料。

  • Blob:Blob就是將二進位制資料做了一個封裝,我們拿到的就是一個整體,可以看到它的整體屬性大小、型別;可以對其分割,但看不到它內部的細節

  • 聯絡:Blob可以接受一個ArrayBuffer作為引數生成一個Blob物件,此行為就相當於對ArrayBuffer資料做一個封裝。

  • 應用上的區別:由於ArrayBufferBlob的特性,Blob作為一個整體檔案,適合用於檔案傳輸;而只有需要關注細節(比如要修改某一段資料時),此時使用ArrayBuffer比較好。

從以上我們的介紹以及聯絡,我們可以得出如下的轉換函式

1
2
3
4
5
6
7
8
/**
** file轉blob
* @param {FileList} files fileList物件
* @param {String} type MIME型別
*/
function fileToBlob(files, type=''){
return new Blob(files, {type});
}
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
/**
* blob轉arrayBuffer,file轉arrayBuffer同理。
* file轉arrayBuffer也可通過FileReader,能控制更多互動細節,在此暫不介紹
**
* @param {Blob} blob blob物件
* @return {Promise} Promise物件
*/
function blobToArrayBuffer(blob) {
return new Promise((resolve, rejejct) => {
blob.arrayBuffer().then(buffer => {
resolve(buffer);
}).catch(err => {
rejejct(err);
});
});
}
 

弄清了他們之間的關係,在以後的工作學習中,才能剛好的去使用這些物件,讓其用在最適用的地方。而不是每次都一頭霧水,熟悉並陌生著。對於和他們相關的FileReader、Base64、FormData,後續會更新相關內容,將其進行聯絡起來,更好的理解他們。

參考資料

相關文章