Blob
Blob虏杰,Binary Large Object的縮寫讥蟆,代表二進(jìn)制類型的大對(duì)象。Mysql中的Blob類型就表示二進(jìn)制數(shù)據(jù)的容器嘹屯,在Web中攻询,Blob對(duì)象是二進(jìn)制數(shù)據(jù),但它是類似文件對(duì)象的二進(jìn)制數(shù)據(jù)州弟,因此可以操作File對(duì)象一樣操作Blob對(duì)象钧栖,實(shí)際上低零,F(xiàn)ile繼承自Blob
Blob、File拯杠、ArrayBuffer掏婶、FileList、FileReader潭陪、DataURL雄妥、BlobURL
- Blob和ArrayBuffer都是用來(lái)存儲(chǔ)二進(jìn)制的,Blob是對(duì)象依溯,ArrayBuffer是數(shù)組老厌,由于ArrayBuffer是一個(gè)二進(jìn)制數(shù)組,所以可以作為Blob對(duì)象的參數(shù):
//為<div> hello world</div>的二進(jìn)制
const u8Buf = new Uint8Array([60, 100, 105, 118, 62, 104, 101, 108, 108, 111, 32, 119, 111, 114, 108, 100, 60, 47, 100, 105, 118, 62]);
const u8Blob = new Blob([u8Buf], { type: "text/html" }); // Blob {size: 22, type: "text/html"}
- ArrayBuffer不能直接操作黎炉,依賴于TypedArray視圖(例如上面的Uint8Array)或者DataView對(duì)象來(lái)解釋原始緩沖區(qū)枝秤。
- Blob對(duì)象可以直接通過(guò)slice進(jìn)行內(nèi)容分片,其本身只有size和type屬性慷嗜。
- File對(duì)象繼承于Blob對(duì)象淀弹,并提供了name(文件名)、size(大小)庆械、type(MIME類型)薇溃、lastModified、lastModifiedDate等信息缭乘。
- FileReader用于異步讀取文件內(nèi)容(用于讀取File沐序、Blob的內(nèi)容)
- FileReader.readAsArrayBuffer(blob):開始讀取指定Blob中的內(nèi)容,一旦完成忿峻,result屬性中保存的數(shù)據(jù)是被讀取文件的ArrayBuffer數(shù)據(jù)薄啥。(將blob轉(zhuǎn)換成ArrayBuffer)
- FileReader.readAsBinaryString(blob):開始讀取Blob內(nèi)容,一旦完成逛尚,result屬性中將包含所讀取文件的原始二進(jìn)制數(shù)據(jù)垄惧。
- FileReader.readAsDataURL(blob):開始讀取指定Blob內(nèi)容,一旦完成绰寞,result屬性中將包含一個(gè)
data:URL
格式的字符串以表示所讀取文件的內(nèi)容(將blob轉(zhuǎn)換成DataURL到逊,轉(zhuǎn)換成base64編碼) - FileReader.readAsText():開始讀取指定Blob內(nèi)容,一旦完成滤钱,result屬性中將包含一個(gè)字符串表示所讀取的文件內(nèi)容
- FileReader.readyState
- EMPTY:0 還沒有加載任何數(shù)據(jù)
- LOADING:1數(shù)據(jù)正在被加載
- DONE:2已完成全部的讀取請(qǐng)求
- FileReader.onload:事件觉壶,該事件在讀取操作完成時(shí)觸發(fā)
- FileRader.onerror:事件,該事件在讀取操作發(fā)生錯(cuò)誤時(shí)觸發(fā)件缸。
- DataURL由FileReader.readAsDataURL(blob)生成铜靶,為一整串base64編碼是一個(gè)完整的數(shù)據(jù)。BlobURL由window.URL.createObjectURL(blob)生成他炊,是一個(gè)類似于HTTP的URL
- FileList通常用于表單提交文件時(shí),為文件的類數(shù)組對(duì)象
<body>
<input type="file" id="file" />
</body>
<script>
window.onload = function () {
var fileInput = document.querySelector("#file")
fileInput.addEventListener("change", function (e) {
console.log(e.target.files)
console.log(this.files)
})
}
</script>
Blob基本用法
創(chuàng)建
通過(guò)Blob的構(gòu)造函數(shù)創(chuàng)建Blob對(duì)象:
Blob(blobParts[,options])
- blobParts:為數(shù)組争剿,數(shù)組中的每一項(xiàng)連接起來(lái)構(gòu)成Blob對(duì)象的數(shù)據(jù)已艰,數(shù)組中的每項(xiàng)元素可以是ArrayBuffer,ArrayBufferView,Blob,DOMString蚕苇。
- options:可選項(xiàng)哩掺,置頂MIME類型和結(jié)束符方式
- type,默認(rèn)值為 ""涩笤,它代表了將會(huì)被放入到blob中的數(shù)組內(nèi)容的MIME類型嚼吞。
- endings,默認(rèn)值為"transparent"蹬碧,用于指定包含行結(jié)束符\n的字符串如何被寫入舱禽。 它是以下兩個(gè)值中的一個(gè): "native",表示行結(jié)束符會(huì)被更改為適合宿主操作系統(tǒng)文件系統(tǒng)的換行符恩沽; "transparent"呢蔫,表示會(huì)保持blob中保存的結(jié)束符不變。
var data1 = "a";
var data2 = "b";
var data3 = "<div style='color:red;'>This is a blob</div>";
var data4 = { "name": "abc" };
var blob1 = new Blob([data1]);
var blob2 = new Blob([data1, data2]);
var blob3 = new Blob([data3]);
var blob4 = new Blob([JSON.stringify(data4)]);
var blob5 = new Blob([data4]);
var 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: ""}
console.log(blob4); //輸出:Blob {size: 14, type: ""}
console.log(blob5); //輸出:Blob {size: 15, type: ""}
console.log(blob6); //輸出:Blob {size: 59, type: ""}
size代表Blob對(duì)象中所包含數(shù)據(jù)的字節(jié)數(shù)
使用字符串和使用對(duì)象創(chuàng)建Blob是不同的飒筑,例如blob4通過(guò)JSON.stringify
把data4對(duì)象轉(zhuǎn)換成JSON字符串,而blob5則直接使用對(duì)象創(chuàng)建绽昏,兩個(gè)blob對(duì)象的size分別為14和15协屡。
blob4的結(jié)果為"{"name":"abc"}"剛好是14個(gè)字節(jié)。
blob5的記過(guò)為"[object Object]"是15個(gè)字節(jié)全谤。
實(shí)際上肤晓,當(dāng)使用普通對(duì)象創(chuàng)建Blob對(duì)象時(shí),相當(dāng)于調(diào)用了普通對(duì)象的toString()
方法得到字符串?dāng)?shù)據(jù)认然,然后在再創(chuàng)建Blob對(duì)象补憾。
slice分片方法
Blob對(duì)象有一個(gè)sloce方法,放回一個(gè)新的Blob對(duì)象卷员,包含了源Blob對(duì)象中范圍內(nèi)的數(shù)據(jù)盈匾。
slice([start[,end[,contentType]]])
- start:起始下標(biāo),表示第一個(gè)會(huì)被拷貝進(jìn)新的Blob字節(jié)的其實(shí)位置毕骡。如果是一個(gè)負(fù)數(shù)削饵,那么這個(gè)偏移量將會(huì)從數(shù)據(jù)的末尾從后道歉開始計(jì)算。
- end:結(jié)束下標(biāo)未巫,如果傳入負(fù)數(shù)窿撬,偏移量會(huì)從數(shù)據(jù)的末尾從后到前開始計(jì)算。
- contentType:新的Blob對(duì)象的文檔類型叙凡,默認(rèn)值為一個(gè)空的字符串劈伴。
var data = "abcdef"
var blob1 = new Blob([data])
var blob2 = blob1.slice(0,3)
//輸出:Blob {size:6,type:""}
console.log(blob1);
//輸出:Blob {size:3,type:""}
console.log(blob2);
Blob使用場(chǎng)景
文件分片上傳
File繼承自Blob,所以我們可以用slice方法對(duì)大文件進(jìn)行分片長(zhǎng)傳
function uploadFile(file){
//每片大小為1M
var chunkSize = 1024*1024
var totalSize = file.size
//分片總數(shù)
var chunckQuantity = Math.ceil(totalSize/chunkSize)
//偏移量
var offset = 0
var reader = new FileReader()
//設(shè)置文件onload回調(diào)
reader.onload = function(e){
var xhr = new XMLHttpRequest()
xhr.open("POST","http://xxx/upload?fileName="+file.name)
xhr.overrideMimeType("application/octet-stream")
xhr.onreadystatechange = function(){
if(xhr.readState === XMLHttpRequest.DONE && xhr.status === 200){
++offset
if(offset === chunkQuantity){
//上傳完成
}else if(offset === chunckQuantity){
//上傳最后一片握爷,偏移量結(jié)束點(diǎn)為文件大小
blob = file.slice(offset*chunckSize,totalSize)
reader.readAsBinaryString(blob)
}else{
blob = file.slice(offset*chunckSzie,(offset+1)*chunckSize)
reader.readAsBinaryString(blob)
}
}else{
alert("上傳出錯(cuò)")
}
}
if(xhr.sendAsBinary){
//e.target.result為此次讀取的分片二進(jìn)制數(shù)據(jù)
xhr.sendAsBinary(e.target.result)
}else{
xhr.send(e.targt.result)
}
}
var blob = file.slice(0, chunkSize)
reader.readAsBinaryString(blob)
}
可以進(jìn)一步豐富跛璧,比如上傳進(jìn)度严里,使用多個(gè)XMLHttpRequest對(duì)象并行上傳對(duì)象(需要傳遞分片數(shù)據(jù)的位置參數(shù)給服務(wù)端)等。
Blob URL(資源地址)
Blob URL是Blob協(xié)議的URL:
blob:http://xxx
Blob URL可以通過(guò)URL.createObjectURL(blob)創(chuàng)建赡模,在絕大部分場(chǎng)景下田炭,我們可以像使用HTTP協(xié)議的URL一樣,使用Blob URL漓柑。
常見的場(chǎng)景有:作為文件的下載地址和作為圖片資源地址教硫。
作為文件的下載地址:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Blob Test</title>
<script>
function createDownloadFile() {
var content = "Blob Data";
var blob = new Blob([content]);
var link = document.getElementsByTagName("a")[0];
link.download = "file";
link.href = URL.createObjectURL(blob);
}
window.onload = createDownloadFile;
</script>
</head>
<body>
<a>下載</a>
</body>
</html>
點(diǎn)擊下載按鈕,瀏覽器將會(huì)下載一個(gè)名為file的文件辆布,文件內(nèi)容是Blob Data瞬矩。通過(guò)Blob對(duì)象,在前端就可以動(dòng)態(tài)生成文件锋玲,提供瀏覽器下載景用。
作為圖片資源地址
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Blob Test</title>
<script>
function handleFile(e) {
var file = e.files[0];
var blob = URL.createObjectURL(file);
var img = document.getElementsByTagName("img")[0];
img.src = blob;
img.onload = function(e) {
URL.revokeObjectURL(this.src); // 釋放createObjectURL創(chuàng)建的對(duì)象##
}
}
</script>
</head>
<body>
<input type="file" accept="image/*" onchange="handleFile(this)" />
<br/>
<img style="width:200px;height:200px">
</body>
</html>
在network標(biāo)簽欄下能夠發(fā)現(xiàn)這個(gè)Blob URL的請(qǐng)求信息
Blob URL和Data URL的區(qū)別
還可以使用Data URL方式加載圖片資源:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Blob Test</title>
<script>
function handleFile(e) {
var file = e.files[0];
var fileReader = new FileReader();
var img = document.getElementsByTagName("img")[0];
fileReader.onload = function(e) {
img.src = e.target.result;
}
fileReader.readAsDataURL(file);
}
</script>
</head>
<body>
<input type="file" accept="image/*" onchange="handleFile(this)" />
<br/>
<img style="width:200px;height:200px">
</body>
</html>
FileReader的readAsDataURL生成一個(gè)Data URL,如圖所示:
web性能優(yōu)化中有一項(xiàng)措施惭蹂,把小圖片用base64編碼直接遷入到HTML文件中伞插,實(shí)際上就是利用了Data URL來(lái)獲取嵌入的圖片數(shù)據(jù)。
Blob URL和Data URL的區(qū)別
- Blob URL的長(zhǎng)度一般比較短盾碗,但Data URL因?yàn)橹苯哟鎯?chǔ)圖片base64編碼后的數(shù)據(jù)媚污,往往很長(zhǎng),如上圖所示廷雅,瀏覽器在顯示Data URL時(shí)使用了省略號(hào)(…)耗美。當(dāng)顯式大圖片時(shí),使用Blob URL能獲取更好的可能性
- Blob URL可以方便的使用XMLHttpRequest獲取源數(shù)據(jù)
var blobUrl = URL.createObjectURL(new Blob(['Test'], {type: 'text/plain'}));
var x = new XMLHttpRequest();
// 如果設(shè)置x.responseType = 'blob'航缀,將返回一個(gè)Blob對(duì)象商架,而不是文本:
// x.responseType = 'blob';
x.onload = function() {
alert(x.responseText); // 輸出 Test
};
x.open('get', blobUrl);
x.send();
- Blob URL只能在當(dāng)前應(yīng)用內(nèi)部使用,把Blob URL復(fù)制到瀏覽器的地址中芥玉,是無(wú)法獲取數(shù)據(jù)的(外部無(wú)法獲壬呙)。而Data URL可以在瀏覽器中使用灿巧,具有較好的移植性
指定文件類型
除了可以用作圖片資源的網(wǎng)絡(luò)地址皇型,Blob URL也可以用作其他資源的網(wǎng)絡(luò)地址,例如html文件砸烦、json文件等弃鸦,為了保證瀏覽器能正確的解析Blob URL返回的文件類型,需要在創(chuàng)建Blob對(duì)象時(shí)指定相應(yīng)的type
// 創(chuàng)建HTML文件的Blob URL
var data = "<div style='color:red;'>This is a blob</div>";
var blob = new Blob([data], { type: 'text/html' });
var blobURL = URL.createObjectURL(blob);
// 創(chuàng)建JSON文件的Blob URL
var data = { "name": "abc" };
var blob = new Blob([JSON.stringify(data)], { type: 'application/json' });
var blobURL = URL.createObjectURL(blob);
Blob和ArrayBuffer
ArrayBuffer對(duì)象用來(lái)表示通用的幢痘、固定長(zhǎng)度的原始二進(jìn)制數(shù)據(jù)緩沖區(qū)唬格。
通過(guò)new ArrayBuffer(length)
來(lái)獲得一片連續(xù)的內(nèi)存空間,它不能直接讀寫,但可根據(jù)需要將其傳遞到TypedArray視圖或者DataView對(duì)象來(lái)解釋原始緩沖區(qū)购岗。
實(shí)際上視圖只是給我們提供了一個(gè)某種類型的讀寫接口汰聋,讓我們可以操作ArrayBuffer里的數(shù)據(jù)。
TypedArray需要制定一個(gè)數(shù)組類型來(lái)保證數(shù)組成員都是一個(gè)數(shù)據(jù)類型喊积,而DataView數(shù)組成員可以是不同的數(shù)據(jù)類型烹困。
TypedArray視圖的類型數(shù)組對(duì)象:(他們的構(gòu)造函數(shù)都接收一個(gè)ArrayBuffer參數(shù)進(jìn)行轉(zhuǎn)換,由于ArrayBuffer不能直接讀惹恰)
- Int8Array:8位有符號(hào)整數(shù)髓梅,長(zhǎng)度1個(gè)字節(jié)。
- Uint8Array:8位無(wú)符號(hào)整數(shù)绎签,長(zhǎng)度1個(gè)字節(jié)枯饿。
- Uint8ClampedArray:8位無(wú)符號(hào)整數(shù),長(zhǎng)度1個(gè)字節(jié)诡必,溢出處理不同奢方。
- Int16Array:16位有符號(hào)整數(shù),長(zhǎng)度2個(gè)字節(jié)爸舒。
- Uint16Array:16位無(wú)符號(hào)整數(shù)蟋字,長(zhǎng)度2個(gè)字節(jié)牍蜂。
- Int32Array:32位有符號(hào)整數(shù)吏饿,長(zhǎng)度4個(gè)字節(jié)磁玉。
- Uint32Array:32位無(wú)符號(hào)整數(shù)榛瓮,長(zhǎng)度4個(gè)字節(jié)。
- Float32Array:32位浮點(diǎn)數(shù)远寸,長(zhǎng)度4個(gè)字節(jié)。
- Float64Array:64位浮點(diǎn)數(shù),長(zhǎng)度8個(gè)字節(jié)璧尸。
Blob與ArrayBuffer的區(qū)別是,除了原始字節(jié)以外它還提供了Mime type作為原數(shù)據(jù)熬拒,Blob和ArrayBuffer之間可以進(jìn)行轉(zhuǎn)換爷光,F(xiàn)ile對(duì)象其實(shí)繼承自Blob對(duì)象,并提供了name澎粟、lastModifiedDate蛀序、size、type等基礎(chǔ)元數(shù)據(jù)
Blob對(duì)象轉(zhuǎn)換成ArrayBuffer
//創(chuàng)建一個(gè)以二進(jìn)制數(shù)據(jù)存儲(chǔ)的html文件
const text = "<div>hello world</div>"
const blob = new Blob([text],{type:"text/html"})
//以文本讀取
const textReader = new FileReader()
textReader.readAsText(blob)
textReader.onload = function(){
console.log(textReader.result);//<div> hello word</div>
}
//以ArrayBuffer讀取
const bufReader = new FileReader()
bufReader.readAsArrayBuffer(blob)
bufReader.onload = function(){
console.log(new Uint8Array(bufReader.result)) // Uint8Array(22) [60, 100, 105, 118, 62, 104, 101, 108, 108, 111, 32, 119, 111, 114, 108, 100, 60, 47, 100, 105, 118, 62]
}
ArrayBuffer轉(zhuǎn)換成Blob
const u8Buf = new Uint8Array([60, 100, 105, 118, 62, 104, 101, 108, 108, 111, 32, 119, 111, 114, 108, 100, 60, 47, 100, 105, 118, 62]);
const u8Blob = new Blob([u8Buf], { type: "text/html" }); // Blob {size: 22, type: "text/html"}
const textReader = new FileReader();
textReader.readAsText(u8Blob);
textReader.onload = function() {
console.log(textReader.result); // 同樣得到div>hello world</div>
};
從后臺(tái)獲取Blob(File)
通過(guò)正確的設(shè)置responseType我們可以直接獲取到Blob對(duì)象
function ajax(url,cb){
const xhr = new XMLHttpRequest()
xhr.open("get",url)
//"text"-字符串 "blob"-Blob對(duì)象 "arraybuffer"-ArrayBuffer對(duì)象
xhr.responseType = "blob"
xhr.onload = function(){
cb(xhr.response)
}
xhr.send
}
通過(guò)請(qǐng)求一個(gè)Blob對(duì)象或者ArrayBuffer再轉(zhuǎn)換成Blob對(duì)象活烙,再通過(guò)URL.createObjectURL生成BlobURL賦值給src屬性即可
ajax('video.mp4', function(res){
const src = URL.createObjectURL(res);
video.src = src;
})
MediaSource(流媒體播放徐裸,視頻流)
video標(biāo)簽src指向一個(gè)視頻地址,視頻播完了再將src修改為下一段的視頻地址然后播放啸盏,這顯然不符合我們無(wú)縫播放的要求重贺。其實(shí)有了我們前面Blob URL的學(xué)習(xí),我們可能就會(huì)想到一個(gè)思路,用Blob URL指向一個(gè)視頻二進(jìn)制數(shù)據(jù)气笙,然后不斷將下一段視頻的二進(jìn)制數(shù)據(jù)添加拼接進(jìn)去次企。這樣就可以在不影響播放的情況下,不斷的更新視頻內(nèi)容并播放下去潜圃,想想是不是有點(diǎn)流的意思出來(lái)了缸棵。
要實(shí)現(xiàn)這個(gè)功能我們要通過(guò)MediaSource來(lái)實(shí)現(xiàn),MediaSource接口功能也很純粹谭期,作為一個(gè)媒體數(shù)據(jù)容器可以和HTMLMediaElement進(jìn)行綁定堵第。基本流程就是通過(guò)URL.createObjectURL創(chuàng)建容器的BLob URL崇堵,設(shè)置到video標(biāo)簽的src上型诚,在播放過(guò)程中,我們?nèi)匀豢梢酝ㄟ^(guò)MediaSource.appendBuffer方法往容器里添加數(shù)據(jù)鸳劳,達(dá)到更新視頻內(nèi)容的目的狰贯。
可以理解MediaSource為一個(gè)Blob的容器,可以通過(guò)addSourceBuffer來(lái)創(chuàng)建一個(gè)指定類型的Blob容器赏廓,這個(gè)容器可以通過(guò)appendBuffer不斷的往里面添加數(shù)據(jù)
const video = document.querySelector('video');
//視頻資源存放路徑涵紊,假設(shè)下面有5個(gè)分段視頻 video1.mp4 ~ video5.mp4,第一個(gè)段為初始化視頻init.mp4
const assetURL = "http://www.demo.com";
//視頻格式和編碼信息幔摸,主要為判斷瀏覽器是否支持視頻格式摸柄,但如果信息和視頻不符可能會(huì)報(bào)錯(cuò)
const mimeCodec = 'video/mp4; codecs="avc1.42E01E, mp4a.40.2"';
if ('MediaSource' in window && MediaSource.isTypeSupported(mimeCodec)) {
const mediaSource = new MediaSource();
video.src = URL.createObjectURL(mediaSource); //將video與MediaSource綁定,此處生成一個(gè)Blob URL
mediaSource.addEventListener('sourceopen', sourceOpen); //可以理解為容器打開
} else {
//瀏覽器不支持該視頻格式
console.error('Unsupported MIME type or codec: ', mimeCodec);
}
function sourceOpen () {
const mediaSource = this;
const sourceBuffer = mediaSource.addSourceBuffer(mimeCodec);
let i = 1;
function getNextVideo(url) {
//ajax代碼實(shí)現(xiàn)翻看上文既忆,數(shù)據(jù)請(qǐng)求類型為arraybuffer
ajax(url, function(buf) {
//往容器中添加請(qǐng)求到的數(shù)據(jù)驱负,不會(huì)影響當(dāng)下的視頻播放。
sourceBuffer.appendBuffer(buf);
});
}
//每次appendBuffer數(shù)據(jù)更新完之后就會(huì)觸發(fā)
sourceBuffer.addEventListener("updateend", function() {
if (i === 1) {
//第一個(gè)初始化視頻加載完就開始播放
video.play();
}
if (i < 6) {
//一段視頻加載完成后患雇,請(qǐng)求下一段視頻
getNextVideo(`${assetURL}/video${i}.mp4`);
}
if (i === 6) {
//全部視頻片段加載完關(guān)閉容器
mediaSource.endOfStream();
URL.revokeObjectURL(video.src); //Blob URL已經(jīng)使用并加載跃脊,不需要再次使用的話可以釋放掉。
}
i++;
});
//加載初始視頻
getNextVideo(`${assetURL}/init.mp4`);
};