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 | 只读 | 返回 |
size | 只读 | 返回 |
lastModified | 只读 | 返回 |
lastModifiedDate | 只读 | 返回 |
webkitRelativePath | 只读 | 返回 File 相关的 |
示例:
<!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 | 只读 | 表示 |
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() | 中止读取操作。只有当 | reader.abort() |
readAsArrayBuffer(blob) | 读取指定的 | reader.readAsArrayBuffer(blob) |
readAsDataURL(blob) | 读取指定的 | reader.readAsArrayBuffer(file) |
readAsBinaryString(blob) | 已废弃,用 readAsArrayBuffer 代替 – | |
readAsText(blob[, encoding]) | 读取指定的 | 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])