1、前提
这几天在开发过程中,遇到了下载excel文件问题,其中服务端返回二进制文件流,需要前端自己对二进制文件流进行转换,用到了new Blob()方式,便上网查阅相关资料。
2、Blob对象
2.1、概念介绍
Blob 全称:Binary Large Object(二进制大型对象)
Blob 对象是一个前端的一个专门用于支持文件操作的二进制对象,表示一个二进制文件的数据内容,表示一个不可变、原始数据的类文件对象。通常用来读写文件,比如一个图片文件的内容就可以通过 Blob 对象读写。Blob 对象。Blob 表示的不一定是JavaScript原生格式的数据。
而在前端工程中,我们通常在下面方式获得Blob对象:
(图片源于知乎@澎湖湾)
2.2、Blob() 函数的参数
浏览器原生提供Blob() 构造函数,用来生成实例。Blob 的内容由参数数组中给出的值的串联组成。
new Blob(array[, options])
Blob构造函数接受两个参数:
- 第一个参数
(必填):是一个包含实际数据的数组,数组的成员可以是字符串或二进制对象,表示新生成的Blob实例对象的内容。(成员可以是一个由ArrayBuffer,ArrayBufferView,Blob,DOMString等对象构成的 Array ,或者其他类似对象的混合体,它将会被放进 Blob。DOMStrings会被编码为UTF-8) - 第二个参数
(可选):是一个配置对象,表示数据的MIME 类型。options包含两个属性:type和endings,默认空字符串。这里介绍常用的属性 type
let new_blob = new Blob(['hello world'], {type : 'text/html'})
这里data :一个包含 DOMString 的数组
这里options :指定数据类型为 text/html
new_blob是一个blob实例
const obj = { hello: 'wang' };
const blob = new Blob([ JSON.stringify(obj) ], {type : 'application/json'})
这里data :一个包含 JSON数据 的数组
这里options :指定数据类型为 application/json
new_blob是一个blob实例
2.3、blob实例的属性
blob 对象也具有两个实例属性:
- size:blob
对象的数据大小,即文件的大小,单位为字节 - type:blob
对象所包含数据的MIME类型。如果类型无法确定,则返回空字符串
console.log(new Blob(['hello world'], {type : 'text/html'}))
//Blob {size: 11, type: "text/html"}
let obj = {name:'wang'};
let new_blob = new Blob([ JSON.stringify(obj) ], {type : 'application/json'})
console.log(new_blob)
//Blob {size: 15, type: "application/json"}
2.4、blob实例的方法
blob 对象可以通过slice方法得到一个新的 blob对象
const newBlob = oldBlob.slice([start [, end [, contentType]]])
slice 方法接收三个可选参数:
start和end都是数值,表示截取的范围contentType指定截取的内容的MIME 类型。返回一个新的 Blob对象。
var blob = new Blob(['hello world'], {type: 'text/plain'});
console.log(blob.size); //11
var newBlob = blob.slice(3, 7, 'text/plain');
console.log(newBlob.size); //4
3、File对象
3.1、概念介绍
在JS中,有两个构造函数:File 和 Blob,而File继承了所有Blob的属性。所以在我们看来,File对象可以看作一种特殊的Blob对象,继承了 blob 的功能并将其扩展使其支持用户系统上的文件。
通常情况下,在前端工程中,我们通常在下面方式获得File对象:
- File 对象是来自用户在一个
<input>元素上选择文件后返回的FileList 对象 - 来自由拖放操作生成的
DataTransfer对象
3.2、File函数的参数
我们接触的多数关于 File 的操作都是读取,js也为我们提供了手动创建 File 对象的构造函数:
File(bits, name[, options])。
- 参数一
bits (必须):参数的类型必须是ArrayBuffer,ArrayBufferView,Blob,或者Array[string]或者任何这些对象的组合。这是 UTF-8 编码的文件内容。 - 参数二
name(必须):参数的类型为字符串String,表示文件名称,或者文件路径 - 参数三
options(可选):参数的类型为对象。表示选项对象,包含文件的可选属性:type 和 lastModified。可用的选项如下:
(1)type------string, 表示将要放到文件中的内容的MIME类型。默认值为空字符串
(2)lastModified------数值,表示文件最后修改时间的 Unix时间戳(毫秒)。默认值为Date.now()。
var file = new File(['text1', 'text2'], 'test.txt', {type: 'text/plain'});
console.log('--file1--',file)
// File { lastModified: 1631434865697
// lastModifiedDate: Sun Sep 12 2021 16:21:05 GMT+0800 (中国标准时间) {}
// name: "test.txt"
// size: 10
// type: "text/plain"
// webkitRelativePath: ""
// }
根据已有的 blob 对象创建 File 对象:
var content1 = ['Hello world'];
var blob1 = new Blob(content1, {type: 'text/plain'});
console.log('--blob1--',blob1)
var file2 = new File([blob1], 'test.png', {type: 'image/png'});
console.log('--file2--',file2)
//--blob1-- △ Blob {size: 11, type: "text/plain"}
//--file2-- △ File {lastModified: 1631435132101
// lastModifiedDate: Sun Sep 12 2021 16:25:32 GMT+0800 (中国标准时间) {}
// name: "test.png"
// size: 11
// type: "image/png"
// webkitRelativePath: ""}
3.3、File实例的属性
File 对象的实例内容不可见,但是有以下属性可以访问:
| 属性名称 | 读 / 写 | 描述 |
|---|---|---|
| name | 只读 | 返回文件的名称,由于安全原因,返回的值并不包含文件路径 |
| type | 只读 | 返回 File 对象,表示文件的媒体类型(MIME)。例如 PNG 图像是 “image/png” |
| size | 只读 | 返回文件的大小 |
| lastModified | 只读 | 返回number,返回所引用文件最后修改日期,自 1970年1月1日0:00 以来的毫秒数 |
| lastModifiedDate | 只读 | 返回Date,返回当前文件的最后修改日期,如果无法获取到文件的最后修改日期,则使用当前日期来替代 |
| webkitRelativePath | 只读 | 返回 File 相关的path 或 URL |
示例:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<div>
<input type="file" id='f' />
</div>
<script>
document.getElementById('f').addEventListener('change', function(event){
console.log('-this.files-',this.files)
const file = this.files[0];
if (file) {
console.log('file.name',file.name);
console.log('file.size',file.size);
console.log('file.lastModified',file.lastModified);
console.log('file.lastModifiedDate',file.lastModifiedDate);
}
});
</script>
</body>
</html>
这是我们选择一张图片之后,控制台打印情况
备注:
- 基于当前的实现,浏览器
不会实际读取文件的字节流,来判断它的媒体类型,它基于文件扩展来假设 - 将 PNG 图像文件的后缀名重命名为 .txt,那么读取的该文件的 type 属性值为 “text/plain”, 而不是 “image/png”
- file.type 仅仅对`常见文件类型可靠。例如图像、文档、音频和视频。不常见的文件扩展名会返回空字符串。开发者最好不要依靠这个属性,作为唯一的验证方案
3.4、File实例的方法
File 对象没有定义额外的方法,由于继承了 Blob 对象,也就继承了 slice方法,用法同上文 Blob 的 slice 方法。
slice([start[, end[, contentType]]])
slice 方法接收三个可选参数:
start和end都是数值,表示截取的范围contentType指定截取的内容的MIME 类型。返回一个新的 Blob对象。
FileReader ,URL.createObjectURL() , createImageBitmap() ,和XMLHttpRequest.send() 都能处理 Blob 和 File。
var file1 = new File(['text1', 'text2'], 'test.txt', {type: 'text/plain'});
console.log(file1)
//△File{
// lastModified: 1631437933620
// lastModifiedDate: Sun Sep 12 2021 17:12:13 GMT+0800 (中国标准时间) {}
// name: "test.txt"
// size: 10
// type: "text/plain"
// webkitRelativePath: ""
//}
var file2 = file1.slice(3,6,'text/plain')
console.log(file2)
//△Blob {
// size: 3,
// type: "text/plain"
//}
4、FileReader对象
4.1、概念介绍
FileReader 对象允许Web应用程序异步读取存储在用户计算机上的文件(或原始数据缓冲区)的内容,使用 File 或 Blob 对象指定要读取的文件或数据。
其中 File 对象,可以是来自用户在一个 <input> 元素上选择文件后返回的 FileList,也可以来自拖放操作生成的 DataTransfer 对象,还可以是来自在一个 HTMLCanvasElement 上执行 mozGetAsFile() 方法后返回结果。
4.2、FileReader构造函数
var reader = new FileReader()
new FileReader()构造函数不需要传入参数,返回一个 FileReader 的实例。FileReader 继承 EventTarget对象。
4.3、FileReader实例的属性
FileReader实例属性
| 属性名称 | 读/写 | 描述 |
|---|---|---|
| error | 只读 | DOMException 的实例,表示在读取文件时发生的错误 。 |
| result | 只读 | 表示文件的内容,该属性仅在读取操作完成后(load)后才有效,格式取决于读取方法 |
| readyState | 只读 | 表示读取文件时状态的数字 |
备注:readeyState的取值如下:
0------EMPTY---------还没有加载任何数据1------LOADING------数据正在被加载2------DONE-----------已完成全部的读取请求
var reader = new FileReader();
console.log(reader.error); // null
console.log(reader.result); // null
console.log(reader.readyState); // 0
console.log(reader.EMPTY); // 0
console.log(reader.LOADING); // 1
console.log(reader.DONE); // 2
EMPTY、LOADING、DONE 这三个属性同时存在于 FileReader 和它的的原型对象上,因此实例上有这三个属性,FileReader 对象本身也有这三个属性:
console.log(FileReader.EMPTY); // 0
console.log(FileReader.LOADING); // 1
console.log(FileReader.DONE); // 2
4.4、FileReader事件
文件的读取是一个异步的过程,和 XMLHttpRequest 对象一样,在读取操作过程中会触发一系列事件。
| 事件名称 | 描述 | 使用示例 |
|---|---|---|
| abort | 读取操作被中断时触发 | reader.onabort = function(event) {} |
| error | 在读取操作发生错误时触发 | reader.onerror = function(event) {} |
| load | 读取操作完成时触发 | reader.addEventListener(‘load’, function(event) {}) |
| loadstart | 读取操作开始时触发 | reader.onloadstart = function(event) {} |
| loadend | 读取操作结束时(要么成功,要么失败)触发 | reader.onloadend = function(event) {} |
| progress | 在读取Blob时触发 | reader.onprogress = function(event) {} |
4.5、FileReader实例方法
关于FileReader实例的方法,具有以下几种:
| 方法名称 | 描述 | 使用示例 |
|---|---|---|
| abort() | 中止读取操作。只有当 readyState 为 1 时才能调用;调用后,readyState 值为 2 在返回时,readyState属性为DONE。 | reader.abort() |
| readAsArrayBuffer(blob) | 读取指定的 Blob 或 File 对象中的内容。读取操作完成后,(触发loadend事件),result属性中保存的是被读取文件的 ArrayBuffer 数据对象,表示所读取的文件的数据内容。 | reader.readAsArrayBuffer(blob) |
| readAsDataURL(blob) | 读取指定的 Blob 或 File 对象中的内容。读取操作完成后,(触发loadend事件),result属性中将保存一个 data:URL格式的(base64编码字符串) ,以表示所读取文件的数据内容。 | reader.readAsArrayBuffer(file) |
| readAsBinaryString(blob) | 已废弃,用 readAsArrayBuffer 代替 – | |
| readAsText(blob[, encoding]) | 读取指定的 Blob 或 File 对象中的内容,将 Blob 或者 File 对象根据特殊的编码格式转化为特定内容(字符串形式),默认编码是 utf-8 。读取完成后,result属性中将包含一个字符串,以表示所读取的文件内容。 | reader.readAsArrayBuffer(blob) |
【读取本地图片示例】:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<div>
<input type="file" id='file' accept="image/png, image/jpg, image/jpeg, image/gif" />
<img src="" alt="Image preview...">
</div>
<script>
var preview = document.querySelector('img');
var reader = new FileReader();
document.getElementById('file').addEventListener('change', function (event) {
console.log('--files--',this.files[0])
var file = this.files[0];
if (file) {
reader.readAsDataURL(file);
}
});
console.log('---reader--222---',reader)
reader.addEventListener("load", function () {
console.log('---reader.result---',reader.result)
preview.src = reader.result;
}, false);
</script>
</body>
</html>

- 可以看出,当我们选择完某张图片后,FileReader的实例reader的result属性值已经通过readAsDataURL方法,变成了一个
data: URL格式的Base64字符串(截图中没有展示完整) - dataURL是base64编码的数据格式,展示类型为
字符串,形如:data:image/jpeg;base64,/9j/4QXERXhpZgAATU······
对于上面的示例,还可以使用URL.createObjectURL的方法
objectURL = URL.createObjectURL(object)
其中:
- 参数
object:指用于创建URL的File 对象、Blob 对象或者MediaSource 对象 - 返回值
objectURL:一个DOMString,包含了一个对象URL,该URL可用于指定源 object的内容。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<div>
<input type="file" id='file' accept="image/png, image/jpg, image/jpeg, image/gif" />
<br>
<img src="" alt="Image preview...">
</div>
<script>
var preview = document.querySelector('img');
var reader = new FileReader();
document.getElementById('file').addEventListener('change', function (event) {
console.log('--files--',this.files[0])
var file = this.files[0];
if (file) {
console.log('--url--',URL.createObjectURL(file))
preview.src = URL.createObjectURL(file);
}
});
</script>
</body>
</html>

5、ArrayBuffer对象
5.1、概念介绍
ArrayBuffer 对象用来表示通用的、固定长度的原始二进制数据缓冲区,是ES6 才纳入正式 ECMAScript 规范。
ArrayBuffer 类型化数组,类型化数组是JavaScript操作二进制数据的一个接口。最初为了满足JavaScript与显卡之间大量的、实时的数据交换,它们之间的数据通信必须是二进制的,而不能是传统的文本格式的背景下诞生的
ArrayBuffer 对象代表储存二进制数据的一段内存,它不能直接读写,只能通过视图(TypedArray视图和DataView视图)来读写,它们(视图)会将缓冲区中的数据用特定的格式进行解读,并通过这些格式来读写缓冲区的内容。
5.2、ArrayBuffer函数
浏览器原生提供 ArrayBuffer() 构造函数,用来生成实例。
new ArrayBuffer(length)
- 参数
length:整数,表示二进制数据占用的字节长度 - 返回值:一个
指定大小的ArrayBuffer 对象,其内容被初始化为 0。
let aa = new ArrayBuffer()
console.log(aa)
//下面为打印内容
ArrayBuffer(0)
byteLength: 0
__proto__: ArrayBuffer
[[Int8Array]]: Int8Array(0)
[[Uint8Array]]: Uint8Array(0)
[[Int16Array]]: Int16Array(0)
[[Int32Array]]: Int32Array(0)
[[ArrayBufferByteLength]]: 0
[[ArrayBufferData]]: "0x000000000000"
let aa = new ArrayBuffer(10)//实例对象 buffer 占用 10 个字节
console.log(aa)
//下面为打印内容
ArrayBuffer(10)
byteLength: (...)
__proto__: ArrayBuffer
[[Int8Array]]: Int8Array(10)
[[Uint8Array]]: Uint8Array(10)
[[Int16Array]]: Int16Array(5)
[[ArrayBufferByteLength]]: 10
[[ArrayBufferData]]: "0x002400a6a2b0"
5.3、ArrayBuffer实例的属性
ArrayBuffer 对象有实例属性 byteLength ,表示当前实例占用的内存字节长度(单位字节),一单创建就不可变更(只读`):
const buffer = new ArrayBuffer(32);
buffer.byteLength; // 32
5.4、ArrayBuffer实例的方法
ArrayBuffer.prototype.slice()
ArrayBuffer的实例有一个slice方法,允许将内存区域的一部分,拷贝生成一个新的ArrayBuffer对象。
slice(start , end)的参数:
- start:整数类型,表示
开始复制的位置。默认从 0 开始 - end:整数类型,表示
结束复制的位置(不包括结束的位置)。如果省略,则表示复制到结束。
const buff = new ArrayBuffer(32);
console.log(buff.byteLength);//32
const newBuffer = buff.slice(0, 3);
//以下为打印内容
ArrayBuffer(3)
byteLength: (...)
__proto__: ArrayBuffer
[[Int8Array]]: Int8Array(3)
[[Uint8Array]]: Uint8Array(3)
[[ArrayBufferByteLength]]: 3
[[ArrayBufferData]]: "0x002400a683b0"
除了slice方法,ArrayBuffer对象不提供任何直接读写内存的方法,只允许在其上方建立视图,然后通过视图读写。
5.5、ArrayBuffer静态方法
ArrayBuffer.isView()
ArrayBuffer本身有一个静态方法isView,返回一个布尔值,表示参数是否为ArrayBuffer的视图实例。这个方法大致相当于判断参数是否为TypedArray实例或DataView实例。
const buffer = new ArrayBuffer(8);
ArrayBuffer.isView(buffer) // false
const v = new Int32Array(buffer);
ArrayBuffer.isView(v) // true
5.6、DataView视图和TypedArray视图
为了读写ArrayBuffer的实例,需要为它指定视图
ArrayBuffer对象作为内存区域,可以存放多种类型的数据。同一段内存,不同数据有不同的解读方式,这就叫做“视图”(view)
ArrayBuffer有两种视图,
- TypedArray视图:数组成员都是
同一个数据类型,TypedArray视图是用来向网卡、声卡之类的本机设备传送数据,所以使用本机的字节序就可以了 - DataView视图:数组成员可以是
不同的数据类型,DataView视图是用来处理网络设备传来的数据,所以大端字节序或小端字节序是可以自行设定的 - 两者的区别主要是
字节序
DataView实例提供8个方法读取内存。
getInt8:读取1个字节,返回一个8位整数。
getUint8:读取1个字节,返回一个无符号的8位整数。
getInt16:读取2个字节,返回一个16位整数。
getUint16:读取2个字节,返回一个无符号的16位整数。
getInt32:读取4个字节,返回一个32位整数。
getUint32:读取4个字节,返回一个无符号的32位整数。
getFloat32:读取4个字节,返回一个32位浮点数。
getFloat64:读取8个字节,返回一个64位浮点数。
var buf = new ArrayBuffer(32);
var dataView = new DataView(buf);
dataView.getUint8(0) // 0
同样,TypedArray对象一共提供9种类型的视图,每一种视图都是一种构造函数。
Int8Array:8位有符号整数,长度1个字节。
Uint8Array:8位无符号整数,长度1个字节。
Uint8ClampedArray:8位无符号整数,长度1个字节,溢出处理不同。
Int16Array:16位有符号整数,长度2个字节。
Uint16Array:16位无符号整数,长度2个字节。
Int32Array:32位有符号整数,长度4个字节。
Uint32Array:32位无符号整数,长度4个字节。
Float32Array:32位浮点数,长度4个字节。
Float64Array:64位浮点数,长度8个字节。
var buffer = new ArrayBuffer(12);
var x1 = new Int32Array(buffer);
x1[0] = 1;
var x2 = new Uint8Array(buffer);
x2[0] = 2;
x1[0] // 2
上面代码对同一段内存,分别建立两种视图:32位带符号整数(Int32Array构造函数)和8位不带符号整数(Uint8Array构造函数)。由于两个视图对应的是同一段内存,一个视图修改底层内存,会影响到另一个视图。
对于DataView视图和TypedArray视图更详细的讲解,可以参考博客:
6、Blob和ArrayBuffer转换
ArrayBuffer 与 Blob 区别:
Blob用于操作二进制文件ArrayBuffer用于操作内存- ArrayBuffer是
原始的二进制数据缓冲区,不能设置MIME类型;Blob可以储存大量的二进制编码格式的数据,可以设置对象的MIME类型 ArrayBuffer的数据,是可以按照字节去操作的,而Blob的只能作为一个整的对象去处理。ArrayBuffer相比Blob更接近真实的二进制,更底层
6.1、Blob 转 ArrayBuffer:
此处需要借助fileReader对象:
let blob = new Blob([1,2,3,4])
let reader = new FileReader();
reader.onload = function(result) {
console.log(result);
}
reader.readAsArrayBuffer(blob);

6.2、ArrayBuffer 转 Blob
arraybuffer转blob很方便,作为参数传入就行了
var buffer = new ArrayBuffer(16)
var blob = new Blob([buffer])

本文部分参考博客: