一文读懂Blob

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也将会有更大的舞台。

©️2020 CSDN 皮肤主题: 1024 设计师:上身试试 返回首页