1.介绍
提起Blob大部分前端同学可能比较生疏,Blob全称binary large object即二进制大对象,顾名思义Blob用于存储二进制数据。在前端领域中Blob赋予了我们直接操作二进制数据的能力,Blob不一定是JavaScript原生格式的数据。Blob最主要的两个特点:
- 数据量大,一般用来表示文件如图片、视频等;
- 不透明,对Blob只能做切割操作,不支持单字节读写;
下面介绍下Blob的方法和属性以及Blob在前端领域的使用场景。
2.基本使用方法
2.1 创建
现代浏览器可以通过Blob构造函数直接创建Blob,Blob构造函数接受两个参数。
blobParts: 数组, 其中每一项可以是字符串、ArrayBuffer、Blob
options: 对象(可选)
options.type:标识存入Blob对象的MIME类型如: application/octet-stream
const blob = new Blob(blobParts, options);
2.2 属性
Blob.size: Blob对象数据大小(单位是字节)
Blob.type: 标识Blob对象数据的MIME类型,默认是空
2.3 方法
slice: 由旧的Blob对象切割生成新的Blob对象,类似于普通数组的slice方法。
slice可接受三个可选参数:
start: 切割起始位置(可选)默认值为0,
end: 切割结束位置(可选)默认值为blob对象长度
contentType: 新Blob的MIME类型
const blob = new Blob(blobParts, options);
const newBlob = blob.slice(start, end, contentType);
3.其他相关概念
下面介绍一些经常和Blob对象一块提起且在后面使用场景中用到的几个对象。
3.1 ArrayBuffer
ArrayBuffer是内存中固定长度的二进制数据,相比于普通数组来说速度会快很多原因是普通数组存于堆中而ArrayBuffer存于栈中,另外ArrayBuffer是值传递。
初始化ArrayBuffer
new ArrayBuffer(length); // length: 要创建的ArrayBuffer的大小,以字节为单位。
从概念上看起来ArrayBuffer和Blob都用来存储二进制数据,那么它们又有什么区别呢?
Blob更关注的是数据的整体,不会对具体某个字节做读写操作适合传输文件,ArrayBuffer更底层一些且可以关注细节比如对某个具体的字节进行读写,ArrayBuffer可以和Blob互相转化。
Blob转ArrayBuffer
const blob = new Blob([1,1,2,2,3,3])
const reader = new FileReader();
reader.onload = (result) => {
const buffer = result.target.result; // 转换ArrayBuffer结果
console.log(buffer);
}
reader.readAsArrayBuffer(blob);
ArrayBuffer转Blob
const buffer = new ArrayBuffer(4); // 分配一个4字节的内存区域
const blob = new Blob([buffer]);
3.2 DataView
上面说到ArrayBuffer可以存二进制数据,但是ArrayBuffer不能直接读写,需要借助DataView视图。
读数据
const buffer = new ArrayBuffer(24);
const dv = new DataView(buffer);
dv.getUint8(0); // 从第1个字节读取一个8位无符号整数,默认是0
写数据
const buffer = new ArrayBuffer(24);
const dv = new DataView(buffer);
dv.setInt8(0, 10); // 向第1个字节写值为10的8位整数
DataView视图可以从ArrayBuffer中读写多种数值类型
DataView视图可以从ArrayBuffer中读写多种数值类型
4.使用场景
Blob可以满足一些和文件传输相关场景下的前端需求,如文件上传、多媒体资源加密等。
4.1 文件上传
得益于h5的飞速发展,图片上传对于前端不再是一个复杂的事情了。XMLHttpRequest2提供了FormData对象,而FormData就支持添加Blob对象。
通过浏览器的input(type=“file”)标签可以获取用户本地的文件,而获取到的数据为File对象,这里FIile就继承自Blob。
下面是一段简单的文件上传代码。
<input id="fileInput" type="file"></input>
const fileInputElement = document.getElementById('fileInput')
fileInputElement.addEventListener('change', (e) => uploadFile(e.target.files[0]))
function uploadFile(file) {
const fd = new FormData();
fd.append('foo', 'bar');
fd.append('file', file);
api.uploadFile(fd);
}
上面的实现方式在文件较大时会存在以下问题:
- web服务器一般会限制单次请求请求体的大小,当文件大小超过这个限制时就上传失败;
- 大文件上传一部分失败后,需要重新上传浪费资源;
因为File继承自Blob对象,那么Blob的slice方法File当然可以使用,那么就可以借助于File的slilce方法实现分片上传。
<input id="fileInput" type="file"></input>
const fileInputElement = document.getElementById('fileInput');
fileInputElement.addEventListener('change', (e) => {
uploadFile(e.target.files[0], 1)
})
function uploadFile(file, i) {
const size = file.size;
const partSize = 1 * 1024 * 1024; // 以1M分片
const count = Math.ceil(size / partSize);
const packet = file.slice(i * partSize, (i + 1) * partSize); //将文件进行切片
const fd = new FormData();
fd.append('file', packet);
fd.append('fileId', Math.random());
fd.append('fileName', file.name);
fd.append('partIndex', index);
fd.append('partSize', count);
api.uploadFile(fd)
.then(({data}) => {
if(data.status === 200){
console.log(data.url) // 上传完毕拿到最终url
}else if (data.status === 201){
uploadFile(file, i + 1); // 上传下一片
}else if(data.status === 500){
uploadFile(file, i); // 上传失败继续上传
}
});
}
4.2 canvas转图片
前端经常遇到使用canvas绘制图片的需求,比如图片编辑,用户编辑完图片需要预览并上传至服务器。其中一个方法就是将canvas转化为Blob对象然后上传至后端。
<canvas id="canvas" width="100" height="100"></canvas>
<img src="" alt="" id="img">
const canvas = document.getElementById('canvas');
const imgEle = document.getElementById('img');
const ctx = canvas.getContext('2d');
ctx.fillStyle= 'red';
ctx.fillRect(0,0,100,100);
canvas.toBlob((blob) => {
const url = URL.createObjectURL(blob);
imgEle.onload = function() {
URL.revokeObjectURL(url);
};
imgEle.src=url;// 图片预览
uploadFile(blob); // 图片上传
})
function uploadFile(){} // 同4.1
4.3 语音播放
一次接入公司ASR(语音识别)SDK时,SDK同学给的语音数据是PCM(声音模拟信号经过模数转换形成的二进制数据)数组,而浏览器不支持PCM格式。
浏览器可以播放WAV格式的语音,而PCM与WAV不同点就是WAV在PCM数据的头部加了44位,将PCM转成WAV然后转成Blob获取Blob链接就可以播放了。
上图为WAV格式的存储结构,把PCM数组当做其中的data部分,然后在头部加上固定的44位信息就组成了浏览器可播放的WAV数据格式。正如上面介绍ArrayBuffer时所说,Blob关注整体,ArrayBuffer可以对具体某个字节修改,这里要转换数据格式就要借助于ArrayBuffer和DataView,添加完头部信息以后再转成可以播放的Blob url格式。
另外在转换过程中还涉及声道数、采样频率、采样位数等参数可参考Web端音频编辑插件实践(一)—音频解析
function pcmToWav(data, {sampleRateTmp, sampleBits, channelCount}){
samples = arrayToBuffer(data, sampleBits); // 将array转为ArrayBuffer
const dataLength = samples.byteLength;
const buffer = new ArrayBuffer(44 + dataLength);
const view = new DataView(buffer); // 因为ArrayBuffer不能直接直接存取所以需要使用DataView视图
let offset = 0;
/* 资源交换文件标识符 */
writeString(view, offset, 'RIFF'); offset += 4;
/* 下个地址开始到文件尾总字节数,即文件大小-8 */
view.setUint32(offset, /* 32 */ 36 + dataLength, true); offset += 4;
/* WAV文件标志 */
writeString(view, offset, 'WAVE'); offset += 4;
/* 波形格式标志 */
writeString(view, offset, 'fmt '); offset += 4;
/* 过滤字节,一般为 0x10 = 16 */
view.setUint32(offset, 16, true); offset += 4;
/* 格式类别 (PCM形式采样数据) */
view.setUint16(offset, 1, true); offset += 2;
/* 通道数 */
view.setUint16(offset, channelCount, true); offset += 2;
/* 采样率,每秒样本数,表示每个通道的播放速度 */
view.setUint32(offset, sampleRateTmp, true); offset += 4;
/* 波形数据传输率 (每秒平均字节数) 通道数×每秒数据位数×每样本数据位/8 */
view.setUint32(offset, sampleRateTmp * channelCount * (sampleBits / 8), true); offset += 4;
/* 快数据调整数 采样一次占用字节数 通道数×每样本的数据位数/8 */
view.setUint16(offset, channelCount * (sampleBits / 8), true); offset += 2;
/* 每样本数据位数 */
view.setUint16(offset, sampleBits, true); offset += 2;
/* 数据标识符 */
writeString(view, offset, 'data'); offset += 4;
/* 采样数据总数,即数据总大小-44 */
view.setUint32(offset, dataLength, true); offset += 4;
if (sampleBits === 8) {
input = new Int8Array(samples);
for (let i = 0; i < input.length; i += 1, offset += 1) {
view.setInt8(offset, input[i]);
}
} else if (sampleBits === 16) {
input = new Int16Array(samples);
for (let i = 0; i < input.length; i += 1, offset += 2) {
view.setInt16(offset, input[i], true);
}
} else {
input = new Int32Array(samples);
for (let i = 0; i < input.length; i += 1, offset += 4) {
view.setInt32(offset, input[i], true);
}
}
return window.URL.createObjectURL(new Blob([view.buffer], {type: 'audio/wav'}));
}
function arrayToBuffer(array, sampleBits) {
const isBuffer = array.byteLength;
if (isBuffer) return array;
if (sampleBits === 8) {
return new Int8Array(array).buffer;
} if (sampleBits === 16) {
return new Int16Array(array).buffer;
}
return new Int32Array(array).buffer;
}
function writeString(dv, offset, string) {
for (let i = 0; i < string.length; i++) {
dv.setUint8(offset + i, string.charCodeAt(i));
}
}
调用pcmToWav方法就可以把PCM数据转化为WAV的Blob Url进行播放。
const pcmArray = [12,2.....];
const Audio = new Audio();
Audio.src = (pcmToWav(pcmArray));
Audio.play();
5.总结
本文介绍了Blob对象的操作及其在前端领域几个常见使用场景。但是由于安全性限制等诸多因素,浏览器对js操作文件有诸多限制,但是相信随着浏览器的发展越来越多的文件API将会提供给js调用,未来Blob也将会有更大的舞台。