Skip to content

上一节阐述了如何声明 ArrayBuffer。而且其声明时的数值均为 0

但在二进制的世界中,如何看待二进制的字节数,其结果是不同的。

譬如,长度为 8 的一串二进制 01010101 可以看做以下几种结果:

  1. 01010101
  2. 01010101
  3. 01010101
  4. 01010101

所以在这种情况下,我们能理解到的是:

ArrayBuffer二进制集合本身数据是没有意义的,除非提供了规则解释器

这里的规则解释器,也称作视图

视图对象本身并不存储任何东西。它是一副眼镜,透过它来解释存储在 ArrayBuffer 中的字节。

视图有两种:

  1. TypedArray 类型化数组。
  2. DataView 数据视图。

TIP

在计算机中,一些信息可以用 1 个字节表示,可以用 2 个字节表示,甚至可以用 4 个字节表示。

也就是说二进制数据本身的含义在不同的规则(视图)下,其表示的含义是不同的

所以这就是为什么 ArrayBuffer 只有在特定视图才有意义。

1.TypedArray

JavaScript 中并不存在名为 TypedArray 的构造函数。

这里的 TypedArray 是一类构造函数的总称:

  1. Uint8Array: 8 位无符号整数。将每个字节视为 0255 之间的单个数字(每个字节是 8 位,因此只能容纳那么多)。
  2. Uint16Array: 16 位无符号整数。将每 2 个字节视为一个 065535 之间的整数。
  3. Uint32Array: 32 位无符号整数将每 4 个字节视为一个 0 到 4294967295 之间的整数。
  4. Uint8ClampedArray: 用于 8 位整数,在赋值时便固定其值。
  5. Int8Array: 8 位无符号整数
  6. Int16Array: 16 位无符号整数
  7. Int32Array: 32 位无符号整数
  8. Float32Array: 32 位有符号浮点数
  9. Float64Array: 64 位有符号浮点数

TIP

这些类型化数组的行为类似于常规数组:具有索引,并且是可迭代的

本文中的 new TypedArray,它表示 new Uint8Arraynew Int8Array 及其他中之一。

1-1.语法

TypedArray 的使用方法有以下五种:

js
// 1.以arrayBuffer为基础创建类型化数组 可以根据可选参数设置偏移量和指定长度
new TypedArray(arrayBuffer, [byteOffset], [length])
// 2.创建一个与目标数组相同长度的类型化数组,并复制其内容
new TypedArray(array)
// 3.类型数组之间互相转化
new TypedArray(typedArray)
// 4.创建指定长度的类型化数组
new TypedArray(length)
// 5.创建长度为零的类型化数组
new TypedArray()

我们在此依次举例说明下前三种使用方式:

  1. arrayBuffer 为基础创建类型化数组,可以根据可选参数设置偏移量和指定长度。
js
var arrayBuffer = new ArrayBuffer(8)
var arr = new Uint16Array(arrayBuffer)
console.log(arr)

这里我们声明了字节长度为 8arrayBuffer,数值默认均为 0

而后通过 16 位类型化数组视图(将两个字节视作一个数据)来描述它,最终视图数据长度为 4:

  1. 创建一个与目标数组相同长度的类型化数组,并复制其内容
js
var arr = new Uint16Array([72, 101, 108, 108, 111])
console.log(arr)

  1. 类型数组之间互相转化
js
var arr16 = new Uint16Array([254, 255, 256])
console.log(arr16)
var arr8 = new Uint8Array(arr16)
console.log(arr8)

这里要说明的一个现象是,可能存在越界行为

譬如,在 Uint8Array 下的数据范围 [0, 256),所以 256Uint8Array 下转化时,数据会截取丢失

100000000 被截取为 00000000,所以转化结果是 0

TIP

Uint8ClampedArray 在这方面比较特殊,它的表现不太一样。

对于大于 255 的任何数字,它将保存为 255,对于任何负数,它将保存为 0

此行为对于图像处理很有用

1-2.方法

TypedArray 具有常规的 Array 方法。

可以遍历(iterate)、mapslicefindreduce

但有两个例外:

  • 没有 splice —— 我们无法“删除”一个值,因为类型化数组是缓冲区(buffer)上的视图,并且缓冲区(buffer)是固定的、连续的内存区域。我们所能做的就是分配一个零值。
  • concat 方法。

另外,它还特有两种方法:

  • arr.set(fromArr, [offset]): 从 offset(默认为 0)开始,将 fromArr 中的所有元素复制到 arr
  • arr.subarray([begin, end]): 创建一个从 beginend(不包括)相同类型的新视图。这类似于 slice 方法(同样也支持),但不复制任何内容,只是创建一个新视图,以对给定片段的数据进行操作。

2.DataView

相比于类型化数组 TypedArrayDataView 的使用更加灵活。

对于类型化数组,构造器决定了其格式。整个数组应该是统一的。第 i 个数字是 arr[i]

而通过 DataView,我们可以使用 getUint8(i)getUint16(i) 之类的方法访问数据。

我们在调用方法时选择格式,而不是在构造的时候。

2-1.语法

其语法只有一种:

js
new DataView(buffer, [byteOffset], [byteLength])
  • buffer —— 底层的 ArrayBuffer。与类型化数组不同,DataView 不会自行创建缓冲区(buffer)。我们需要事先准备好。
  • byteOffset —— 视图的起始字节位置(默认为 0)。
  • byteLength —— 视图的字节长度(默认至 buffer 的末尾)。
js
// 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 可以轻松访问它们。