參考
[HTML5] Blob對象
理解DOMString、Document、FormData、Blob、File、ArrayBuffer數(shù)據(jù)類型
HTML5 File API — 讓前端操作文件變的可能
一、Blob與ArrayBuffer
在jsmpeg系列一 基礎知識 字節(jié)序 ArrayBuffer TypedArray中介紹了處理二進制數(shù)組ArrayBuffer,現(xiàn)在來看一下Blob。Blob是Binary large object的縮寫,它與ArrayBuffer的區(qū)別是除了raw bytes以外它還提供了mime type作為元數(shù)據(jù)。但它依然是無法直接被讀寫的??匆幌聵嬙旌瘮?shù):
var aBlob = new Blob( array[, options])
// array可以為ArrayBuffer、ArrayBufferView、Blob、DOMStrings這些,或者他們的各種組合
// options: 包含type(如:MIME類型)和ending。
在ArrayBuffer vs Blob and XHR2中有如下回復:
Blobs (according to spec anyway) have space for a MIME and easier to put into the HTML5 file API than other formats (it's more native to it).
You would use an ArrayBuffer when you need a typed array because you intend to work with the data, and a blob when you just need the data of the file
在Binary World:ArrayBuffer、Blob以及他們的應用中有描述:
ArrayBuffer似乎是Blob的底層,Blob內部使用了ArrayBuffer。并且構造好的一個Blob實體就是一個raw data。既然用途差不多,那為什么一個Blob一個ArrayBuffer呢?當然,設計Blob和ArrayBuffer的目的是不同的。因為ArrayBuffer更底層,所以它專注的是細節(jié),比如說按字節(jié)讀寫文件。相反,Blob更像一個整體,它不在意細節(jié):就是那么一個原始的Binary Data,你只要來回傳輸就行了。
二、Blob構造方式
1、創(chuàng)建一個裝填DOMString對象的Blob對象

2、創(chuàng)建一個裝填ArrayBuffer對象的Blob對象

3、創(chuàng)建一個裝填ArrayBufferView對象的Blob對象(ArrayBufferView可基于ArrayBuffer創(chuàng)建,返回值是一個類數(shù)組。如下:創(chuàng)建一個8字節(jié)的ArrayBuffer,在其上創(chuàng)建一個每個數(shù)組元素為2字節(jié)的“視圖”)

4.通過Blob.slice()
此方法返回一個新的Blob對象,包含了原Blob對象中指定范圍內的數(shù)據(jù)
Blob.slice(start:number, end:number, contentType:string)
start:開始索引,默認為0
end:截取結束索引(不包括end)
contentType:新Blob的MIME類型,默認為空字符串

5.通過canvas.toBlob()
var canvas = document.getElementById("canvas");
canvas.toBlob(function(blob){
console.log(blob);
});

三、DOMString
跟著XMLHttpRequest闖南走北很多年,看名字似乎很囂張且高深莫測。實際上,在JavaScript中,DOMString就是String。規(guī)范解釋說DOMString指的是UTF-16字符串,而JavaScript正是使用了這種編碼的字符串,因此,在Ajax中,DOMString就等同于JS中的普通字符串。
大家應該都與XMLHttpRequest中數(shù)據(jù)返回屬性之responseText打過交道吧,按照我的理解,這廝就是與DOMString數(shù)據(jù)類型發(fā)生關系的,表明返回的數(shù)據(jù)是常規(guī)字符串。
四、File
FileList 對象針對表單的 file 控件。當用戶通過 file 控件選取文件后,這個控件的 files 屬性值就是 FileList 對象。它在結構上類似于數(shù)組,包含用戶選取的多個文件。如果 file 控件沒有設置 multiple 屬性,那么用戶只能選擇一個文件,F(xiàn)ileList 對象也就只有一個元素了。
<input type='file' multiple />
<script>
document.querySelector('input').onchange = function() {
console.log(this.files);
};
</script>
比如我選擇了兩個文件,控制臺打?。?/p>
FileList {0: File, 1: File, length: 2}
0: File
1: File
length:2
__proto__: Object

- name:文件名,該屬性只讀。
- size:文件大小,單位為字節(jié),該屬性只讀。
- type:文件的 MIME 類型,如果分辨不出類型,則為空字符串,該屬性只讀。
- lastModified:文件的上次修改時間,格式為時間戳。
- lastModifiedDate:文件的上次修改時間,格式為 Date 對象實例。
五、FileReader
FileReader 的實例擁有 4 個方法,其中 3 個用以讀取文件,另一個用來中斷讀取。下面的表格列出了這些方法以及他們的參數(shù)和功能,需要注意的是 ,無論讀取成功或失敗,方法并不會返回讀取結果,這一結果存儲在 result屬性中。
| 方法名 | 參數(shù) | 描述 |
|---|---|---|
| abort | none | 中斷讀取 |
| readAsBinaryString | file | 將文件讀取為二進制碼 |
| readAsDataURL | file | 將文件讀取為 DataURL |
| readAsText | file, [encoding] | 將文件讀取為文本 |
readAsText:該方法有兩個參數(shù),其中第二個參數(shù)是文本的編碼方式,默認值為 UTF-8。這個方法非常容易理解,將文件以文本方式讀取,讀取的結果即是這個文本文件中的內容。
readAsBinaryString:該方法將文件讀取為二進制字符串,通常我們將它傳送到后端,后端可以通過這段字符串存儲文件。
readAsDataURL:這是例子程序中用到的方法,該方法將文件讀取為一段以 data: 開頭的字符串,這段字符串的實質就是 Data URL,Data URL是一種將小文件直接嵌入文檔的方案。這里的小文件通常是指圖像與 html 等格式的文件。
// 填充選擇的圖片到展示區(qū)
var img = document.createElement("img");
img.classList.add("obj");
img.file = file;
preview.appendChild(img);
// 讀取File對象中的內容
var reader = new FileReader();
reader.onload = (function (aImg) {
return function (e) {
aImg.src = e.target.result;
};
})(img);
reader.readAsDataURL(file);
六、URL
//blob參數(shù)是一個File對象或者Blob對象.
var objecturl = window.URL.createObjectURL(blob);
上面的代碼會對二進制數(shù)據(jù)生成一個 URL,這個 URL 可以放置于任何通??梢苑胖?URL 的地方,比如 img 標簽的 src 屬性。需要注意的是,即使是同樣的二進制數(shù)據(jù),每調用一次 URL.createObjectURL 方法,就會得到一個不一樣的 URL。
這個 URL 的存在時間,等同于網(wǎng)頁的存在時間,一旦網(wǎng)頁刷新或卸載,這個 URL 就失效。(File 和 Blob 又何嘗不是這樣呢)除此之外,也可以手動調用 URL.revokeObjectURL 方法,使 URL 失效。
window.URL.revokeObjectURL(objectURL);
舉個簡單的例子。
var blob = new Blob(["Hello hanzichi"]);
var a = document.createElement("a");
a.href = window.URL.createObjectURL(blob);
a.download = "a.txt";
a.textContent = "Download";
document.body.appendChild(a);
頁面上生成了一個超鏈接,點擊它就能下載一個名為 a.txt 的文件,里面的內容是 Hello hanzichi。
這里插點題外話,簡單介紹下 H5 新增的 download 屬性。對于一些諸如 exe,rar 等瀏覽器不能直接打開的文件類型,我們一般可以直接用一個 a 標簽,將其指向文件在服務端的地址,點擊即可下載。但是如果是一些瀏覽器能直接打開的文件,比如 txt,js 等,如果這樣設置一個超鏈接,點擊會直接打開文件,一般我們可以配合后端實現(xiàn),比如用 PHP。
我們再回到 URL 上來。對于 File 或者 Blob 對象,我們可以這樣理解,它們的存在,依賴于頁面,而 URL 能給這些 "轉瞬即逝" 的二進制對象一個臨時的指向地址。這個臨時的地址還有什么用呢?也能做圖片預覽。
<input type='file' multiple /><br/>
<img />
<script>
document.querySelector("input").onchange = function() {
var files = this.files;
document.querySelector("img").src = window.URL.createObjectURL(files[0]);
}
</script>
七、Blob應用場景
1.分片上傳
File接口基于Blob,繼承了Blob的功能并進行了擴展,故我們可以像使用Blob一樣使用File對象。通過Blob.slice方法,可以將大文件分片,輪循向后臺提交各文件片段,即可實現(xiàn)文件的分片上傳。分片上傳邏輯如下:
- 獲取要上傳文件的File對象,根據(jù)chunk(每片大小)對文件進行分片
- 通過post方法輪循上傳每片文件,其中url中拼接querystring用于描述當前上傳的文件信息;post body中存放本次要上傳的二進制數(shù)據(jù)片段
- 接口每次返回offset,用于執(zhí)行下次上傳
initUpload();
//初始化上傳
function initUpload() {
var chunk = 100 * 1024; //每片大小
var input = document.getElementById("file"); //input file
input.onchange = function (e) {
var file = this.files[0];
var query = {};
var chunks = [];
if (!!file) {
var start = 0;
//文件分片
for (var i = 0; i < Math.ceil(file.size / chunk); i++) {
var end = start + chunk;
chunks[i] = file.slice(start , end);
start = end;
}
// 采用post方法上傳文件
// url query上拼接以下參數(shù),用于記錄上傳偏移
// post body中存放本次要上傳的二進制數(shù)據(jù)
query = {
fileSize: file.size,
dataSize: chunk,
nextOffset: 0
}
upload(chunks, query, successPerUpload);
}
}
}
// 執(zhí)行上傳
function upload(chunks, query, cb) {
var queryStr = Object.getOwnPropertyNames(query).map(key => {
return key + "=" + query[key];
}).join("&");
var xhr = new XMLHttpRequest();
xhr.open("POST", "http://xxxx/opload?" + queryStr);
xhr.overrideMimeType("application/octet-stream");
//獲取post body中二進制數(shù)據(jù)
var index = Math.floor(query.nextOffset / query.dataSize);
getFileBinary(chunks[index], function (binary) {
if (xhr.sendAsBinary) {
xhr.sendAsBinary(binary);
} else {
xhr.send(binary);
}
});
xhr.onreadystatechange = function (e) {
if (xhr.readyState === 4) {
if (xhr.status === 200) {
var resp = JSON.parse(xhr.responseText);
// 接口返回nextoffset
// resp = {
// isFinish:false,
// offset:100*1024
// }
if (typeof cb === "function") {
cb.call(this, resp, chunks, query)
}
}
}
}
}
// 每片上傳成功后執(zhí)行
function successPerUpload(resp, chunks, query) {
if (resp.isFinish === true) {
alert("上傳成功");
} else {
//未上傳完畢
query.offset = resp.offset;
upload(chunks, query, successPerUpload);
}
}
// 獲取文件二進制數(shù)據(jù)
function getFileBinary(file, cb) {
var reader = new FileReader();
reader.readAsArrayBuffer(file);
reader.onload = function (e) {
if (typeof cb === "function") {
cb.call(this, this.result);
}
}
}
以上是文件分片上傳前端的簡單實現(xiàn),當然,此功能還可以更加完善,如后臺需要對合并后的文件大小進行校驗;或者前端加密文件,全部上傳完畢后后端解密校驗等,此處不做贅述。
2.通過url下載文件
window.URL對象可以為Blob對象生成一個網(wǎng)絡地址,結合a標簽的download屬性,可以實現(xiàn)點擊url下載文件
實現(xiàn)如下:
createDownload("download.txt","download file");
function createDownload(fileName, content){
var blob = new Blob([content]);
var link = document.createElement("a");
link.innerHTML = fileName;
link.download = fileName;
link.href = URL.createObjectURL(blob);
document.getElementsByTagName("body")[0].appendChild(link);
}
執(zhí)行后頁面上會生成此Blob對象的地址,點擊后可下載:

3.通過XHR請求顯示圖片
var xhr = new XMLHttpRequest();
xhr.open("get", "mm1.jpg", true);
xhr.responseType = "blob";
xhr.onload = function() {
if (this.status == 200) {
var blob = this.response; // this.response也就是請求的返回就是Blob對象
var img = document.createElement("img");
img.onload = function(e) {
window.URL.revokeObjectURL(img.src); // 清除釋放
};
img.src = window.URL.createObjectURL(blob);
eleAppend.appendChild(img);
}
}
xhr.send();
4.通過input type=File顯示圖片
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Document</title>
</head>
<body>
<input type="file" id="fileElem" multiple accept="image/*"
style="display:none" onchange="handleFiles(this.files)">
<a href="#" id="fileSelect">Select some files</a>
<div id="fileList">
<p>No files selected!</p>
</div>
<script>
window.URL = window.URL || window.webkitURL;
var fileSelect = document.getElementById("fileSelect"),
fileElem = document.getElementById("fileElem"),
fileList = document.getElementById("fileList");
fileSelect.addEventListener("click", function (e) {
if (fileElem) {
fileElem.click();
}
e.preventDefault(); // prevent navigation to "#"
}, false);
function handleFiles(files) {
if (!files.length) {
fileList.innerHTML = "<p>No files selected!</p>";
} else {
fileList.innerHTML = "";
var list = document.createElement("ul");
fileList.appendChild(list);
for (var i = 0; i < files.length; i++) {
var li = document.createElement("li");
list.appendChild(li);
var img = document.createElement("img");
img.src = window.URL.createObjectURL(files[i]);
img.height = 60;
img.onload = function() {
window.URL.revokeObjectURL(this.src);
}
li.appendChild(img);
var info = document.createElement("span");
info.innerHTML = files[i].name + ": " + files[i].size + " bytes";
li.appendChild(info);
}
}
}
</script>
</body>
</html>