以下是几个主角的简介:
- ArrayBuffer 对象用来表示通用的、固定长度的原始二进制数据缓冲区。大白话其实就是代表一块固定的连续内存数据,虽然名字中带有Array,其实和我们认知的数组没半毛钱关系。并不直接提供读取、写入接口,操作需要通
类型数组 视图
或过DataView 视图
。 -
TypedArray(类型数组),在JavaScript下可用的类型数组有:
Int8Array
、Uint8Array
、Uint8ClampedArray
、Int16Array
、Uint16Array
、Int32Array
、Uint32Array
、Float32Array
、Float64Array
、BigInt64Array
、BigUint64Array
,提供内存数据的不同类数组(array-like)视图形式,底层还是ArrayBuffer
存储数据。大白话就是用数组的形式表示内存数据,并提供按数组索引操作数据。什么作用呢?比如你的数据是单个字节为单位的,那么用Uint8Array
描述一段内存,则可以通过索引操作每个单位的数据,同样,如果你的数据是双字节为单位的,那么你就可以通过Uint16Array
描述这段内存。所以,new Uint8Array([255,255])
与new Uint16Array([255*255])
表示的是一样的数据。 -
DataView,如果说
TypedArray
是以固定的单位读写二进制数据的话,DataView
则灵活多了,在同一个视图中,可以使用多种字节混合的方式,甚至无需考虑不同平台的字节序问题。 -
Blob对象表示一个不可变、原始数据的类文件对象,不像之前的其他仨,
Blob
不属于ES标准,是浏览器API。Blob
可以理解为ArrayBuffer
+mime type
。File对象继承自Blob。
简单说,视图
就是读、写二进制的形式。 ArrayBuffer
对象代表原始的二进制数据,TypedArray
视图用来读写简单类型的二进制数据,DataView
视图用来读写复杂类型的二进制数据。
下图可以直观理解视图
:
上图出处
下面开始详细介绍以上几种数据类型。
ArrayBuffer
ArrayBuffer
仅仅代表一块缓冲区域,并不能读取、写入数据
// 创建10字节的缓冲区,每个二进制位填充为0
const buffer = new ArrayBuffer(10)
// 如果有个现成的buffer,可以通过byteLength读取其大小
buffer.byteLength // 10
ArrayBuffer的来源非常多,举几个常见的场景:
- 来自FileReader
const fileInput = document.getElementById('fileInput');
const file = fileInput.files[0];
const reader = new FileReader();
reader.readAsArrayBuffer(file);
reader.onload = function () {
// 看这里看这里看这里
const buffer = reader.result;
};
- 来自XMLHttpRequest
const xhr = new XMLHttpRequest();
xhr.open('GET', someUrl);
// 声明需要的数据类型
xhr.responseType = 'arraybuffer';
xhr.onload = function () {
// 看这里看这里看这里
const buffer = xhr.response;
};
xhr.send();
- 来自canvas
const canvas = document.createElement('canvas')
canvas.height = 1
canvas.width = 1
const ctx = canvas.getContext('2d')
const buffer = ctx.getImageData(0,0,canvas.width,canvas.height).data.buffer
- 来自Response/Fetch
const buffer = await new Response("Hello World!").arrayBuffer()
// 或者
const response = await fetch('/')
const buffer = await response.arrayBuffer()
- 来自WebSockets
const socket = new WebSocket('ws://127.0.0.1:8081');
// 声明数据交换类型
socket.binaryType = 'arraybuffer';
// Wait until socket is open
socket.addEventListener('open', function (event) {
socket.send(new ArrayBuffer(10));
});
// Receive binary data
socket.addEventListener('message', function (event) {
// 看这里看这里看这里
const buffer = event.data;
});
- 其他可以直接或间接拿到ArrayBuffer的地方
TypedArray
ArrayBuffer
自身是个黑盒,不可读取、写入,那么就需要一种工具来操作ArrayBuffer
,这种工具就叫视图。本节的TypedArray(类型数组)
就是视图的一种。
我们知道二进制最小单位为bit
,只能表示两种状态:0
和1
,绝大多数情况下,我们去操作二进制是以字节(byte字节,1byte=8bit)为单位,还有些情况甚至是以两个字节或四个字节,所以TypedArray
提供了一些列针对特定数据类型的类型化数组的构造函数:Int8Array
、Uint8Array
、Uint8ClampedArray
、Int16Array
、Uint16Array
、Int32Array
、Uint32Array
、Float32Array
、Float64Array
、BigInt64Array
、BigUint64Array
,用于满足不同场景下的需求。
const buffer = new ArrayBuffer(10)
const u8Buffer = new Uint8Array(buffer)
u8Buffer[0] = 200
u8Buffer[1] = 300 // 超过8位最大值256 溢出,值为44
const u16Buffer = new Uint16Array(buffer)
u16Buffer[1] = 300 // 不会溢出,索引对应的最大值为256*256-1
特别的,在颜色计算的场景,我们希望在某值在溢出后不要截断,而直接使用255,这时Uint8ClampedArray
就派上了用场:
const buffer = new ArrayBuffer(10)
const u8Buffer = new Uint8ClampedArray(buffer)
u8Buffer[0] = 200 // 正常 200
u8Buffer[1] = 300 // 溢出,值为 255
每个类型数组都有两个静态方法,用于从数组及不定的参数快速得到对象:
- TypedArray.from()
使用类数组(array-like)或迭代对象创建一个新的类型化数组。
- TypedArray.of()
通过可变数量的参数创建新的类型化数组。
并且,无论哪种类型数组的实例,都有普通数组拥有的许多方法(不包含改变buffer长度的方法),比如:map
、forEach
、reduce
…
DataView
DataView
是除了TypedArray
之外的另一种操作ArrayBuffer
的选择。但是不像TypedArray
提供各种不同的对象来满足不同的需求,DataView
只有一个构造函数,实例化后提供getInt8
、getUint8
、getInt16
、getUint16
、getInt32
、getUint32
、getFloat32
、getFloat64
等读方法与setInt8
、setUint8
、setInt16
、setUint16
、setInt32
、setUint32
、setFloat32
、setFloat64
等写方法。在复杂的场景下,可以用一个视图实例达到不同类型读写目的。
另外,DataView
读、写多字节二进制数据默认使用大端序
,可以通过指定诸如getInt16
或setInt32
之类的多字节操作方法最后一个可选参数为非false
、undefined
值来使用小端序
。
示例:
const buffer = new ArrayBuffer(6)
const view = new DataView(buffer)
view.setUint8(0, 11) // 1
view.setUint16(2, 22) // 2
view.setUint16(4, 33, true) // 3
console.log(new Uint8Array(buffer)) // [11, 0, 0, 22, 33, 0]
console.log(new Uint16Array(buffer)) // [11, 5632, 33, 0, 0]
console.log(view.getUint16(2)) // 22
console.log(view.getUint16(4)) // 8448
console.log(view.getUint16(4, true)) // 33
为什么//2写入的22
,在Uint16Array
视图中是5632
呢?
因为Uint16Array
视图是按小端序读取数据的,而//2中我们是按大端序写入的数据,我们计算验证下:
22
的二进制为10110
,后面再拼接8位0
,得到1011000000000
,转换为十进制刚好是5632
。
view.getUint16(4)
得到8848
也是一样的道理。
Blob
Blob
对象表示一个不可变、原始数据的类文件对象。我们日常使用的File
对象继承了它。
由于Blob
的不可变性,需要将其转换成ArrayBuffer
然后通过视图方可读、写数据。
通过其构造函数,我们可以从如下四种方式得到一个Blob实例:
// 从ArrayBuffer
const buffer = new ArrayBuffer(8)
const blob1 = new Blob([buffer])
// 从ArrayBufferView
const view = new DataView(buffer)
const blob2 = new Blob([view])
// 从另一个Blob对象
const blob3 = new Blob([blob2])
// 从字符串,顺便指定mime
const blob4 = new Blob(["<h1>Hello World</h1>"], {type:"text/html"})
Blob
对象的存在,为我们在浏览器上自行构建file-like(类文件)
对象提供了可能,我们可以通过一定转换将客户端数据通过Blob
生成DataURL
或ObjectURL
,从而可以用于需要url的场景。比如:动态构建文件并下载、本地选择的图片预览等。
转换
各种数据转换如下图:
我们列举一些常见的场景来尝试转换。
本地选择图片实现预览
路径:本地图片->Blob->ObjectURL/DataURL
const fr = new FileReader
fr.onload = e=>{
img.src = e.target.result
}
// 以dataURL形式读取file
fr.readAsDataURL(file)
// blob转成ObjectURL
img.src = URL.createObjectURL(file)
下载一段自定义文本
路径:文本->Blob->ObjectURL/DataURL
const text = "Hello World"
const blob = new Blob(\[text\],{type:"text/plain"})
const url = URL.createObjectURL(blob)
// 省略下载过程
参考: