JS-blob 对象
记录 JS 对象中比较特别的,经常在文件上传中运用的 blob 对象;
前置任务
在了解 blob 之前先来看下密不可分的 JS 二进制类型的数据分类;
关于缓冲区
由于在 JS 里用 Array 这种数据类型存储大字节流很低效,且浪费内存空间,所以在node
里创建了一种数据缓冲区
来操作二进制数据;这是一块物理内存(相对于直接读写硬盘来说更快),用于在数据从一个位置移动到另一位置时使用的临时存储空间,JS 解释器
借助缓冲区读写行。这个概念其实挺像RAM | cache
之间的关系的,我们需要一块速度更快,更小的存储空间来对数据流进行快速的读写切换,毕竟一次性加载特别大的文件(或者大量数据流)是很费时的;
buffer
用过node
的同学应该知道,这是node
用于IO 操作
的时候,比如读取文件,接收请求,通过 buffer 对象创建一个存放二进制数据的缓存区域,对数据进行整合,一个 buffer 对象类似于一个整型数组,对于v8
引擎来说是堆内存之外的一块原始内存。
ArrayBuffer、ArrayBufferView
ArrayBuffer
ArrayBuffer
表示固定长度的二进制数据的原始缓冲区,其构造函数是分配一段连续的内存区域供数据存放,所以对于高密度的访问(如音频数据)操作,比 Array 快,其存在的价值在于作为数据源提前写入到内存里,长度固定,感觉有点类似 C 语言里的malloc
函数,分配一块固定的内存区域;
对于二进制数据,无非就是读写;读取可以通过FileReader
将文件对象转换成ArrayBuffer
对象,实例:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<input type="file" name="" id="f" onchange="fun()" />
<script>
function fun() {
let file = document.querySelector('#f').files[0];
console.warn(file);
let arrBuf;
const reader = new FileReader();
reader.onload = e => {
console.warn(e);
// arrBuf就是ArrayBuffer类型数据
arrBuf = e.target.result;
console.warn(arrBuf);
};
reader.readAsArrayBuffer(file);
}
</script>
ArrayBuffer
对象是只读的,初始化后大小固定,只能借助TypedArray | DataView
写入;
ArrayBuffer
对象有一个属性byteLength
用于表示大小,方法slice
用于截取
ArrayBufferView
由于ArrayBuffer
本身的实例不提供读写方法,所以创建了这个对象作为ArrayBuffer
的视图,指定了原始二进制数据
的基本处理单元,通过ArrayBufferView
来读取实际的内容,上面说的TypedArray | DataView
是该对象的实例。
TypedArray
类型化数组是 JS 中新出现的概念,为了访问原始的二进制数据,本质和ArrayBuffer
一样,只是具备了读写功能;而TypedArray
本身不存在在全局属性中,也没有对应的构造函数,有的都是以下列表中的类型数组;包含了所有相关属性和方法 TypedArray 相关
1
2
3
4
5
6
7
8
9
Int8Array();
Uint8Array();
Uint8ClampedArray();
Int16Array();
Uint16Array();
Int32Array();
Uint32Array();
Float32Array();
Float64Array();
构造函数参数:
length
:缓冲区大小;typedArray
:当传入以上任意的类型化数组对象时,会复制一份新的,但是在复制之前会进行相应的类型转换,比如原来是Int8Array
,新的是Int16Array
,数组中的每一项都进行了转换,并且length
属性不变。object
,传入对象就像通过TypedArray.from()
创建一个新的类型化数组一样;buffer,byteOffset,length
,三个参数可以创建一个类型化数组的视图,后两个参数表示下标偏移量和偏移距离;
由于普通数组对象查找用的是 Hash 方式,而类型数组是直接访问内存,访问速度来说比普通数组要快;而且处理二进制数据的能力更强。这个类型的对象基本拥有数组的所有属性和方法
TypedArrar <-> ArrayBuffer
互相转换:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
// 创建ArrayBuffer对象,只读对象
const arrbuf = new ArrayBuffer(10);
// 利用TypedArray构造函数转换
const int8 = new Int8Array(arrbuf);
// 通过下标
int8[0] = 20;
// 访问元素,支持数组的部分方法
int8.forEach(el => {
console.warn(el);
});
const int16 = new Int16Array(20);
// 通过set方法批量设值,第二个参数是偏移量
int16.set(int8);
int16.set(int8, 10);
简单应用,JS 获取音频,进行拼接;参考的是这个思路fetch请求音频资源 -> ArrayBuffer -> TypedArray -> 拼接成一个 TypedArray -> ArrayBuffer -> Blob -> Object URL
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
<audio id="audio" controls>audio</audio>
<script>
async function audio(params) {
const au = document.querySelector('#audio');
let tmpStream, stream;
let p1, p2;
p1 = fetch('./audio/vue.mp3').then(async res => {
// 转换成ArrayBuffer对象
return await res.arrayBuffer();
});
p2 = fetch('./audio/function.mp3').then(async res => {
return await res.arrayBuffer();
});
let streamLen = 0;
await Promise.all([p1, p2])
.then(async result => {
// console.warn(result);
tmpStream = result.map(el => {
// console.warn(el, el.arrayBuffer());
// streamLen += el.length;
// let ab = await el.arrayBuffer();
// 转换成TypedArray对象
let u8 = new Uint8Array(el);
// 得到音频段落的总长度
streamLen += u8.length;
return u8;
});
})
.catch(err => {});
console.warn(p1, p2);
/* streamLen = tmpStream.reduce((prev, curr) => {
return prev + curr.length;
}, streamLen); */
// 新的TypedArray对象,内存空间是音频总长度
stream = new Uint8Array(streamLen);
let tmpCount = 0;
tmpStream.forEach((el, idx) => {
// 调用set方法,拼接几段音频,注意第二个参数的使用
stream.set(el, tmpCount);
tmpCount += el.length;
/* el.forEach(e => {
}); */
});
console.warn(stream);
// 转换成blob对象,参数可以设置该blob对象的MIME类型,我这里是mp3
let blob = new Blob([stream], {
type: 'audio/mpeg'
});
// 转换成url对象,可以直接赋值给src
let url = URL.createObjectURL(blob);
au.src = url;
}
window.onload = () => {
audio();
};
</script>
DataView
DataView
对象可以在 ArrayBuffer 任意位置读取和存储不同类型的二进制数据。
其构造函数语法为:buffer,byteOffset,byteLength
三个参数可以创建一个类型化数组的视图,后两个参数表示下标偏移量和偏移距离;
对象的属性也是上述的三个参数,且是只读属性,在创建时就固定了。对象的读写方法分别是getInt8 | setInt8
,以及其他类型,入参和数组下标类似,set
方法额外传入元素值。来看例子:
1
2
3
4
5
6
7
let ab = new ArrayBuffer(10);
let dv1 = new DataView(ab);
let dv2 = new DataView(ab);
dv1.setInt16(0, 257);
dv2.getInt16(0); // 257
dv2.getInt8(0); // 1
dv2.getInt8(1); // 1
blob
blob
用来表示二进制类型的对象,是不可变的、原始数据的类文件对象,MDN 关于 blob 对象的介绍。
构造函数:
1
new Blob(blob, options);
blob
:数组类型,可以存放任意多个 ArrayBuffer, ArrayBufferView, Blob 或者 DOMString(会编码为 UTF-8),将它们连接起来构成 Blob 对象的数据。options
:选项参数,设置blob
对象的属性,可选值为:type
:存放到第一个参数的MIME
类型,默认""
;endings
:指定包含行结束符\n
如何被写入;两个可选项,transparent
默认值表示不改变原来的结束符,native
表示根据操作系统来决定;
看几个实例,上面提到了blob
构造函数的第一个参数,是一个数组类型,可以是几个类型的;DOMString
是一个编码为UTF-16
的字符串,因为 JS 已经使用了这样的字符串,所以会被直接映射到一个String
类型。
1
2
3
4
5
6
7
8
9
10
// DOMstring对象
let blob = new Blob(['<p>test</p>'], {
type: 'text/html'
});
// ArrayBuffer
let arrbuf = new ArrayBuffer(8);
let blob1 = new Blob([arrbuf]);
// ArrayBufferView
let typed = new Int8Array(arrbuf);
let blob2 = new Blob([typed]);
Blob
对象有两个属性,size | type
含义显而易见,都是只读属性;
方法有slice | stream | text | arrayBuffer
,第一个是类似数组的 slice 方法,可以用于对较大的二进制数据进行切片,后三个都是读取方法;
上传文件的例子,在input
里选中的文件,都是保存为二进制形式的,可以利用formData
,将二进制文件作为参数传到服务器上;关于formdata
https://developer.mozilla.org/zh-cn/docs/Web/API/FormData
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<input type="file" name="" id="f" onchange="fun()" accept="image/*" />
<script>
function fun() {
let file = document.querySelector('#f').files[0];
console.warn(file);
const formData = new FormData();
formData.append('files', file);
axios
.post('/upload', formData, {
headers: {
'Content-Type': 'multipart/form-data'
}
})
.then(result => {})
.catch(err => {});
}
</script>
File 类
File
对象是一种特殊的blob
对象,继承了所有 blob 属性和方法,也可以直接作为二进制文件上传;
file
对象可以从上面的例子,input
标签中获取,也可以通过拖拽生成的DataTransfer
对象;
通过type='file'
的例子在上面已经有了,现在来写一个通过拖拽生成的file
对象的例子。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
<style lang="">
.drag {
height: 100px;
width: 100px;
border: 5px solid #000;
}
</style>
<div class="drag" ondrop="drop(event)" ondragover="allowDrop(event)"></div>
<script>
function allowDrop(ev) {
ev.preventDefault();
}
function drop(ev) {
ev.preventDefault();
const files = ev.dataTransfer.files;
console.log(files);
console.log(files instanceof FileList);
}
</script>
file
对象的属性包括lastModified | name | size | type
,都是很好理解的属性;方法没有特有的实例方法,都继承了blob
对象的方法;
fileReader
由于file
对象拿到的也是blob
二进制的内容,所以 JS 还提供了fileReader
对象来操作它们,可以把二进制内容转换成其他格式的数据;
readAsText
:将blob
对象转化成文本字符串;readAsArrayBuffer
:转化成 ArrayBuffer 对象;readAsDataURL
:转化成base64
格式的DataURL
;
看个图片上传并预览的例子:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
<input type="file" name="" id="f" onchange="fun()" accept="image/*" />
<script>
function fun() {
let file = document.querySelector('#f').files[0];
console.warn(file);
let arrBuf;
const reader = new FileReader();
reader.onload = e => {
console.warn(e);
arrBuf = e.target.result;
console.warn(arrBuf);
let blob = new Blob([arrBuf], {
type: 'image/*'
});
let src = URL.createObjectURL(blob);
let img = document.createElement('img');
img.setAttribute('src', src);
document.body.appendChild(img);
src = null;
img.onload = function () {
window.URL.revokeObjectURL(this.src);
};
};
// 也可以直接调用这个方法,得到base64格式,可以直接赋值给图片的src属性,不需要像ArrayBuffer那样转格式
// reader.readAsDataURL(file);
reader.readAsArrayBuffer(file);
}
</script>
上面的例子用了readAsArrayBuffer
方法,得到的是ArrayBuffer
对象,所以要转换格式为URL object
,也可以直接readAsDataURL
,就省去了一步转换;
blobURL
blobURL
是一种伪协议,只能由浏览器在内部生成,我们知道script/img/video/iframe
等标签的 src 属性和 background 的 url 可以通过 url 和 base64 来显示,我们同样可以把 blob 或者 file 转换为 url 生成 blobURL
来展示图像,blobURL
允许 Blob 和 File 对象用作图像,下载二进制数据链接等的 URL 源。base64 编码于解码
下载文件的例子,用的是二进制方式;
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<button onclick="handleClick()">click</button>
<script>
function handleClick() {
let blob = new Blob(['test plain text'], {
type: 'text/plain'
});
const elink = document.createElement('a');
elink.download = 'filename';
elink.style.display = 'none';
elink.href = URL.createObjectURL(blob);
document.body.appendChild(elink);
elink.click();
URL.revokeObjectURL(elink.href);
document.body.removeChild(elink);
}
</script>
核心属性和方法在https://developer.mozilla.org/zh-cn/docs/Web/API/URL
可以看到这种方式下,标签的 src 或者 href 的值会变成以bolb:
开头的一串链接字符串。这个特殊的字符串就是 objectURL;下面介绍另一种特殊的链接;
dataURL
前面的例子中也有出现过,dataURL
也是一个特殊的字符串,用于将小文件嵌入到HTML
文档中,经常在小图片中用到,可以节省一次 HTTP 请求,其语法形如:
1
2
3
4
5
6
data:[<mediatype>][;base64],data
data:前缀;
mediatype:可选,MIME类型的字符串;
base64:可选,标志位;
data:数据本身
获取 dataURL 的方式共有三种,
- fileReader 的方法,readAsDataURL,将二进制数据转化成 base64 格式的 dataURL;
- JS 的编码/解码方法,
atob | btoa
,两者都针对 dataURL 中的 data 部分进行处理; - canvas 提供的
toDataURL
方法,该方法可用于获取 canvas 绘制的内容;
DataURL 可以作为 URL 的替代,和 blobURL 一样可以直接给img/video
标签的src
属性赋值;
区别
看起来 blobURL 和 DataURL 类似,都可以直接通过浏览器地址栏访问,它们之间的区别是:
- blobURL 是唯一的字符串,即使每次传入相同的 blob 参数,生成的也是不同的 blobURL;但是 DataURL 只会随着 blob 参数变化;
- blobURL 不代表数据本身,数据存储在浏览器中,blobURL 只是访问它的 key,数据会一直有效直到关闭浏览器或手动清除;而 DataURL 是编码后的数据本身;所以如果把 blobURL 传递给服务器也是无法访问到数据的;浏览器关闭后可以再打开也可以访问到 DataURL,但是不能访问 blobURL;
- blobURL 长度较短,DataURL 由于会存储编码后的数据,一般比较长(而且由于其编码的特殊性,一般会比二进制数据长 33%),所以如果应用场景中图片较大,选择 blobURL 会比较好;
- blobURL 可以使用 XHR 获取源数据(需要设置响应类型为
blob
类型);对于 DataURL,并不是所有浏览器都支持 XHR 获取源数据; - blobURL 也可以作为其他资源的网络地址,如 HTML,JSON 等,需要注意的是在创建 blob 对象时指定相应的 MIME type;
- DataURL 不会被浏览器缓存,导致了每次访问相同页面,资源都会被下载一次,如果图片被大量重复使用,那用这种方式不太合理。
canvas
canvas
可以在页面中确定一个区域作为画布,调用 JS 方法就可以动态的在画布中绘制图形;
canvas
元素对象的方法有:
toDataURL(type, options)
:以指定格式返回DataURL
;其中type
表示图片格式,默认image/png
;options
表示图片质量,在指定图片格式为 image/jpeg 或 image/webp 的情况下,可以从 0-1 选择图片质量,默认 0.92。toBlob(callback, type, options)
:创建 blob 对象,用于展示 canvas 图片,默认图片类型是 image/png,分辨率是 96dpi;回调函数的参数是blob
对象;getImageData(x, y, width, height)
:返回ImageData
对象,该对象复制了画布指定矩形的像素;参数分别是起始坐标,复制的区域大小;putImageData(imgData,x,y,dirtyX,dirtyY,dirtyWidth,dirtyHeight)
:将指定的imgData
图像数据放到画布上;
canvas 实际应用场景
当需要获取 canvas 内容时,可以用到前两个方法(用于签名,图片裁剪,压缩等),后两个方法可以用于图片灰度或者复制时使用;
各类型间转换
原文总结的类型转换图,挺好的,搬过来:
参考
https://mp.weixin.qq.com/s/OYJJnBfzyR3J4CYWGxgm6w