Web编程技术营地
研究、演示、创新

ArrayBuffer

撰写时间:2023-08-06

修订时间:2026-04-08

VBO中存储的数据类型为ArrayBufferView,它是ArrayBuffer的视图。

关于ArrayBuffer,详见本站的ArrayBuffer

在本章中,我们结合WebGL应用的特性,初步讨论相关内容。

ArrayBuffer

ArrayBuffer是一个通用的、固定长度的二进制缓冲区。它是由多个字节的数据所组成的数组。

ArrayBuffer有一个byteLength属性,代表该对象的字节长度,即多少字节。上面的构造函数,创建了一个字节长度为0的实例。

我们可以在ArrayBuffer的构造函数中传入一个表示字节的数值,以创建一个特定长度的实例。其数据被初始化为相应的0值。

const buffer = new ArrayBuffer(2); pc.log(buffer.byteLength); // 2 bytes

视图

不能直接访问ArrayBuffer的中的数据。因此,如果我们试图查看其内容,将显示undefined

const buffer = new ArrayBuffer(2); pc.log(buffer[0]); pc.log(buffer[1]);

Chrome浏览器中运行以下代码:

const buffer = new ArrayBuffer(2); console.dir(buffer);

将在浏览器的Console中输出:

ArrayBuffer(2) Int8Array(2) UInt8Array(2) Int16Array(1)

可以看到,对于2个字节的ArrayBuffer,可以由2Int8Array构成、或2Uint8Array构成、或1Int16Array构成。这类似于C语言中使用一个char指针指向一个int的数据类型,以便独立地访问int变量中的每个字节。

我们将上面的Int8ArrayUint8ArrayInt16Array这些类型化数组称为ArrayBuffer视图view)。也就是说,对于同一个数据,可以根据需求,使用不同的视图将其内在数据转换为不同的数据类型,并依视图的格式进行读写。

注:还有另外一种称为DataView的视图,提供了以特定字长读取或设置缓冲区数据的方法,但由于这种需求较少,故本章不涉及该类的内容。

下面,我们通过类型化数组Uint8Array来访问ArrayBuffer

const buffer = new ArrayBuffer(2); const view = new Uint8Array(buffer); pc.log(view); pc.log(view.byteOffset); pc.log(view.length);

我们取Uint8Array为视图。由于buffer的字长为2,而一个Uint8Array的字长为1,因此buffer的内容依此视图被分为元素为[0, 1]两个元素的数组。viewbyteOffset0,表示当前指针前向buffer的第0个字节;length2,对应于view这个对象共有2个元素。

类型化数组

上面可以看出,视图Uint8Array的原型是TypedArray

类型化数组 (typed array) 是一种类似于数组的视图,用于表现其所包含的二进制的数据缓冲区。

各种类型化数组

类型化数组共有12种,详见下表。

类名值域字长Web IDL
Int8Array[-128, 127]1byte
Uint8Array[0, 255]1octet
Uint8ClampedArray[0, 255]1octet
Int16Array[-32768, 32767]2short
Uint16Array[0, 65535]2unsigned short
Int32Array[-2147483648, 2147483647]4long
Uint32Array[0, 4294967295]4unsigned long
Float16Array[-65504, 65504]2N/A
Float32Array[-3.4E38, 3.4E38]4unrestricted float
Float64Array[-1.8E308, 1.8E308]8unrestricted double
BigInt64Array[-263, 263-1]8bigint
BigUint64Array[0, 264-1]8bigint

从值域来看,WebGL应用中,对于表示顶点位置、顶点颜色等浮点数值,一般使用Float32Array即可;对于顶点索引,一般使用Uint16Array即可。

构造函数

类型化数组共有4种构造函数。

指定元素个数的构造函数

在构造函数中传入一个整数,以指定数组的元素个数。

let i8Arr = new Int8Array(8); // length: how many elements console.log(i8Arr.length); // 8 elements console.log(i8Arr.BYTES_PER_ELEMENT); // 1 bytes per element console.log(i8Arr.byteLength); // total 1 * 8 = 8 bytes let i16Arr = new Int16Array(8); // length: how many elements console.log(i16Arr.length); // 8 elements console.log(i16Arr.BYTES_PER_ELEMENT); // 2 bytes per element console.log(i16Arr.byteLength); // total 2 * 8 = 16 bytes

注意细微的区别。ArrayBuffer的构造器参数表示字节数,而类型化数组构造器的参数表示元素个数。这是因为ArrayBuffer没有涉及具体的数据类型,无法表示共有多少个特定类型的元素,因此只能用总共的字节数来表示。而对于每种类型化数组,其属性BYTES_PER_ELEMENT已含有每个元素多少字节的信息,因此,只要指定共有多少个元素,就能计算出总的字节长度。

上面的代码new Int8Array(8)创建了一个共有8个元素的Int8Array数组,每个元素的字长为1字节,因此其byteLength等于1 * 8 = 8个字节。

代码new Int16Array(8)创建了一个共有8个元素的Int16Array数组,每个元素的字长为2字节,因此其byteLength等于2 * 8 = 16个字节。

因此,对于一个特定的类型化数组ta,其byteLength表示该类型化数组共占用多少个字节,其与length(数组元素数量)与BYTES_PER_ELEMENT(每个数组元素所占用的字节数)的公式如下:

指定数组数据的构造函数

在构造函数中传入一个数组,以作为类型化数组的内容。

const array = new Float32Array([ 0.0, 0.5, 0.0, -0.5, -0.5, 0.0, 0.5, -0.5, 0.0 ]);

我们之前的例子使用最多的就是这种方式。

指定其他类型化数组的构造函数

在构造函数中传入另一个类型化数组。

let a = new Int8Array(4); let b = new Int16Array(a); console.log(a.length); // 4 console.log(a.BYTES_PER_ELEMENT); // 1 console.log(a.byteLength); // 4 console.log(b.length); // 4 console.log(b.BYTES_PER_ELEMENT); // 2 console.log(b.byteLength); // 8

对于这种方式,两个类型化数组的元素数量及其数据一致,但由于各自的BYTES_PER_ELEMENT不一样,导致最后的byteLength不一样。

指定ArrayBuffer的构造函数

const buffer = new ArrayBuffer(2); let view1 = new Uint8Array(buffer); let view2 = new Uint16Array(buffer);

这种方式,是以2种类型化数组的视图来共享同一个数据缓冲区。

参考资源

  1. MDN Endianness
  2. MDN DataView