上一节阐述了如何声明 ArrayBuffer
。而且其声明时的数值均为 0
。
但在二进制的世界中,如何看待二进制的字节数,其结果是不同的。
譬如,长度为 8
的一串二进制 01010101
可以看做以下几种结果:
0
、1
、0
、1
、0
、1
、0
、1
01
、01
、01
、01
0101
、0101
01010101
所以在这种情况下,我们能理解到的是:
ArrayBuffer二进制集合本身数据是没有意义的,除非提供了规则解释器。
这里的规则解释器,也称作视图。
视图对象本身并不存储任何东西。它是一副眼镜,透过它来解释存储在 ArrayBuffer
中的字节。
视图有两种:
TypedArray
类型化数组。DataView
数据视图。
TIP
在计算机中,一些信息可以用 1
个字节表示,可以用 2
个字节表示,甚至可以用 4
个字节表示。
也就是说二进制数据本身的含义在不同的规则(视图)下,其表示的含义是不同的。
所以这就是为什么 ArrayBuffer
只有在特定视图才有意义。
1.TypedArray
在 JavaScript
中并不存在名为 TypedArray
的构造函数。
这里的 TypedArray
是一类构造函数的总称:
Uint8Array
: 8 位无符号整数。将每个字节视为0
到255
之间的单个数字(每个字节是8
位,因此只能容纳那么多)。Uint16Array
: 16 位无符号整数。将每2
个字节视为一个0
到65535
之间的整数。Uint32Array
: 32 位无符号整数将每4
个字节视为一个 0 到 4294967295 之间的整数。Uint8ClampedArray
: 用于8
位整数,在赋值时便固定其值。Int8Array
: 8 位无符号整数。Int16Array
: 16 位无符号整数。Int32Array
: 32 位无符号整数。Float32Array
: 32 位有符号浮点数。Float64Array
: 64 位有符号浮点数。
TIP
这些类型化数组的行为类似于常规数组:具有索引,并且是可迭代的。
本文中的 new TypedArray
,它表示 new Uint8Array
、new Int8Array
及其他中之一。
1-1.语法
TypedArray
的使用方法有以下五种:
// 1.以arrayBuffer为基础创建类型化数组 可以根据可选参数设置偏移量和指定长度
new TypedArray(arrayBuffer, [byteOffset], [length])
// 2.创建一个与目标数组相同长度的类型化数组,并复制其内容
new TypedArray(array)
// 3.类型数组之间互相转化
new TypedArray(typedArray)
// 4.创建指定长度的类型化数组
new TypedArray(length)
// 5.创建长度为零的类型化数组
new TypedArray()
我们在此依次举例说明下前三种使用方式:
- 以
arrayBuffer
为基础创建类型化数组,可以根据可选参数设置偏移量和指定长度。
var arrayBuffer = new ArrayBuffer(8)
var arr = new Uint16Array(arrayBuffer)
console.log(arr)
这里我们声明了字节长度为 8
的 arrayBuffer
,数值默认均为 0
。
而后通过 16
位类型化数组视图(将两个字节视作一个数据)来描述它,最终视图数据长度为 4
:
- 创建一个与目标数组相同长度的类型化数组,并复制其内容
var arr = new Uint16Array([72, 101, 108, 108, 111])
console.log(arr)
- 类型数组之间互相转化
var arr16 = new Uint16Array([254, 255, 256])
console.log(arr16)
var arr8 = new Uint8Array(arr16)
console.log(arr8)
这里要说明的一个现象是,可能存在越界行为。
譬如,在 Uint8Array
下的数据范围 [0, 256)
,所以 256
在 Uint8Array
下转化时,数据会截取丢失。
即 100000000
被截取为 00000000
,所以转化结果是 0
。
TIP
Uint8ClampedArray
在这方面比较特殊,它的表现不太一样。
对于大于 255
的任何数字,它将保存为 255
,对于任何负数,它将保存为 0
。
此行为对于图像处理很有用。
1-2.方法
TypedArray
具有常规的 Array
方法。
可以遍历(iterate
)、map
、slice
、find
和 reduce
。
但有两个例外:
- 没有
splice
—— 我们无法“删除”一个值,因为类型化数组是缓冲区(buffer
)上的视图,并且缓冲区(buffer
)是固定的、连续的内存区域。我们所能做的就是分配一个零值。 - 无
concat
方法。
另外,它还特有两种方法:
arr.set(fromArr, [offset])
: 从offset
(默认为0
)开始,将fromArr
中的所有元素复制到arr
。arr.subarray([begin, end])
: 创建一个从begin
到end
(不包括)相同类型的新视图。这类似于slice
方法(同样也支持),但不复制任何内容,只是创建一个新视图,以对给定片段的数据进行操作。
2.DataView
相比于类型化数组 TypedArray
,DataView
的使用更加灵活。
对于类型化数组,构造器决定了其格式。整个数组应该是统一的。第 i
个数字是 arr[i]
。
而通过 DataView
,我们可以使用 getUint8(i)
或 getUint16(i)
之类的方法访问数据。
我们在调用方法时选择格式,而不是在构造的时候。
2-1.语法
其语法只有一种:
new DataView(buffer, [byteOffset], [byteLength])
buffer
—— 底层的ArrayBuffer
。与类型化数组不同,DataView
不会自行创建缓冲区(buffer
)。我们需要事先准备好。byteOffset
—— 视图的起始字节位置(默认为0
)。byteLength
—— 视图的字节长度(默认至buffer
的末尾)。
// 4 个字节的二进制数组,每个都是最大值 255
let buffer = new Uint8Array([255, 255, 255, 255]).buffer;
let dataView = new DataView(buffer);
// 在偏移量为 0 处获取 8 位数字
alert( dataView.getUint8(0) ); // 255
// 现在在偏移量为 0 处获取 16 位数字,它由 2 个字节组成,一起解析为 65535
alert( dataView.getUint16(0) ); // 65535(最大的 16 位无符号整数)
// 在偏移量为 0 处获取 32 位数字
alert( dataView.getUint32(0) ); // 4294967295(最大的 32 位无符号整数)
dataView.setUint32(0, 0); // 将 4 个字节的数字设为 0,即将所有字节都设为 0
当混合格式的数据存储在同一缓冲区(buffer
)中时,DataView
非常有用。
例如,当我们存储一个成对序列(16
位整数,32
位浮点数)时,用 DataView
可以轻松访问它们。