ArrayBuffer
、类型化数组、DataView
«ElementType»Array.from()
new ArrayBuffer()
ArrayBuffer
的静态方法ArrayBuffer.prototype
的属性TypedArray<T>
的静态方法TypedArray<T>.prototype
的属性new «ElementType»Array()
«ElementType»Array
的静态属性«ElementType»Array.prototype
的属性new DataView()
DataView.prototype
的属性Web 上的许多数据都是文本:JSON 文件、HTML 文件、CSS 文件、JavaScript 代码等。JavaScript 通过其内置字符串可以很好地处理此类数据。
但是,在 2011 年之前,它不能很好地处理二进制数据。类型化数组规范 1.0 于 2011 年 2 月 8 日推出,提供了用于处理二进制数据的工具。在 ECMAScript 6 中,类型化数组被添加到核心语言中,并获得了以前仅适用于普通数组的方法(.map()
、.filter()
等)。
类型化数组的主要用例是
ArrayBuffer
、类型化数组、DataView
类型化数组 API 将二进制数据存储在 ArrayBuffer
的实例中
const buf = new ArrayBuffer(4); // length in bytes
// buf is initialized with zeros
ArrayBuffer 本身是一个黑盒子:如果您想访问它的数据,必须将其包装在另一个对象中——一个*视图对象*。有两种视图对象可用
Uint8Array
:元素是无符号 8 位整数。*无符号*表示它们的范围从零开始。Int16Array
:元素是有符号 16 位整数。*有符号*表示它们有符号,可以是负数、零或正数。Float32Array
:元素是 32 位浮点数。Uint8
、Int16
、Float32
等),您可以在任何字节偏移量处读取和写入这些类型。图 20 显示了 API 的类图。
类型化数组的使用方式与普通数组非常相似,但有一些显著区别
以下代码显示了创建相同类型化数组的三种不同方法
// Argument: Typed Array or Array-like object
const ta1 = new Uint8Array([0, 1, 2]);
const ta2 = Uint8Array.of(0, 1, 2);
const ta3 = new Uint8Array(3); // length of Typed Array
0] = 0;
ta3[1] = 1;
ta3[2] = 2;
ta3[
.deepEqual(ta1, ta2);
assert.deepEqual(ta1, ta3); assert
const typedArray = new Int16Array(2); // 2 elements
.equal(typedArray.length, 2);
assert
.deepEqual(
assert.buffer, new ArrayBuffer(4)); // 4 bytes typedArray
const typedArray = new Int16Array(2);
.equal(typedArray[1], 0); // initialized with 0
assert1] = 72;
typedArray[.equal(typedArray[1], 72); assert
DataView 的使用方法如下
const dataView = new DataView(new ArrayBuffer(4));
.equal(dataView.getInt16(0), 0);
assert.equal(dataView.getUint8(0), 0);
assert.setUint8(0, 5); dataView
元素 | 类型化数组 | 字节 | 描述 | |
---|---|---|---|---|
Int8 |
Int8Array |
1 | 8 位有符号整数 | ES6 |
Uint8 |
Uint8Array |
1 | 8 位无符号整数 | ES6 |
Uint8C |
Uint8ClampedArray |
1 | 8 位无符号整数 | ES6 |
(钳制转换) | ES6 | |||
Int16 |
Int16Array |
2 | 16 位有符号整数 | ES6 |
Uint16 |
Uint16Array |
2 | 16 位无符号整数 | ES6 |
Int32 |
Int32Array |
4 | 32 位有符号整数 | ES6 |
Uint32 |
Uint32Array |
4 | 32 位无符号整数 | ES6 |
BigInt64 |
BigInt64Array |
8 | 64 位有符号整数 | ES2020 |
BigUint64 |
BigUint64Array |
8 | 64 位无符号整数 | ES2020 |
Float32 |
Float32Array |
4 | 32 位浮点数 | ES6 |
Float64 |
Float64Array |
8 | 64 位浮点数 | ES6 |
表 20 列出了可用的元素类型。这些类型(例如,Int32
)出现在两个位置
在类型化数组中,它们指定元素的类型。例如,Int32Array
的所有元素都具有 Int32
类型。元素类型是类型化数组中唯一不同的方面。
在 DataView 中,当您使用 .getInt32()
和 .setInt32()
等方法时,它们是访问其 ArrayBuffer 的镜头。
元素类型 Uint8C
很特殊:它不受 DataView
支持,仅用于启用 Uint8ClampedArray
。此类型化数组由 canvas
元素使用(它替换了 CanvasPixelArray
),否则应避免使用。Uint8C
和 Uint8
之间的唯一区别在于如何处理溢出和下溢(如下一小节中所述)。
类型化数组和数组缓冲区使用数字和大整数来导入和导出值
类型 BigInt64
和 BigUint64
通过大整数处理。例如,setter 接受大整数,getter 返回大整数。
所有其他元素类型都通过数字处理。
通常,当值超出元素类型的范围时,将使用模运算将其转换为范围内的值。对于有符号和无符号整数,这意味着
以下函数有助于说明转换是如何工作的
function setAndGet(typedArray, value) {
0] = value;
typedArray[return typedArray[0];
}
无符号 8 位整数的模转换
const uint8 = new Uint8Array(1);
// Highest value of range
.equal(setAndGet(uint8, 255), 255);
assert// Overflow
.equal(setAndGet(uint8, 256), 0);
assert
// Lowest value of range
.equal(setAndGet(uint8, 0), 0);
assert// Underflow
.equal(setAndGet(uint8, -1), 255); assert
有符号 8 位整数的模转换
const int8 = new Int8Array(1);
// Highest value of range
.equal(setAndGet(int8, 127), 127);
assert// Overflow
.equal(setAndGet(int8, 128), -128);
assert
// Lowest value of range
.equal(setAndGet(int8, -128), -128);
assert// Underflow
.equal(setAndGet(int8, -129), 127); assert
钳制转换不同
const uint8c = new Uint8ClampedArray(1);
// Highest value of range
.equal(setAndGet(uint8c, 255), 255);
assert// Overflow
.equal(setAndGet(uint8c, 256), 255);
assert
// Lowest value of range
.equal(setAndGet(uint8c, 0), 0);
assert// Underflow
.equal(setAndGet(uint8c, -1), 0); assert
每当一个类型(例如 Uint16
)存储为多个字节的序列时,*字节序*就很重要
Uint16
值 0x4321 存储为两个字节——首先是 0x43,然后是 0x21。Uint16
值 0x4321 存储为两个字节——首先是 0x21,然后是 0x43。字节序往往在每个 CPU 架构上都是固定的,并且在原生 API 中是一致的。类型化数组用于与这些 API 通信,这就是为什么它们的字节序遵循平台的字节序并且不能更改的原因。
另一方面,协议和二进制文件的字节序各不相同,但每个格式在不同平台上都是固定的。因此,我们必须能够以任何一种字节序访问数据。DataView 服务于此用例,并允许您在获取或设置值时指定字节序。
其他排序也是可能的。这些通常称为*中端序*或*混合端序*。
在本节中,«ElementType»Array
代表 Int8Array
、Uint8Array
等。ElementType
是 Int8
、Uint8
等。
«ElementType»Array.from()
此方法具有类型签名
.from<S>(
: Iterable<S>|ArrayLike<S>,
source?: S => ElementType, thisArg?: any)
mapfn: «ElementType»Array
.from()
将 source
转换为 this
的实例(一个类型化数组)。
例如,普通数组是可迭代的,可以使用此方法进行转换
.deepEqual(
assertUint16Array.from([0, 1, 2]),
Uint16Array.of(0, 1, 2));
类型化数组也是可迭代的
.deepEqual(
assertUint16Array.from(Uint8Array.of(0, 1, 2)),
Uint16Array.of(0, 1, 2));
source
也可以是*类数组对象*
.deepEqual(
assertUint16Array.from({0:0, 1:1, 2:2, length: 3}),
Uint16Array.of(0, 1, 2));
可选的 mapfn
允许您在 source
的元素成为结果的元素之前对其进行转换。为什么要一次性执行*映射*和*转换*这两个步骤?与通过 .map()
单独映射相比,有两个优点
继续阅读以了解第二个优点的说明。
静态方法 .from()
可以选择同时进行映射和在类型化数组类型之间转换。如果您使用该方法,出错的可能性较小。
为了了解原因,让我们首先将类型化数组转换为精度更高的类型化数组。如果我们使用 .from()
进行映射,结果会自动正确。否则,您必须先转换再映射。
const typedArray = Int8Array.of(127, 126, 125);
.deepEqual(
assertInt16Array.from(typedArray, x => x * 2),
Int16Array.of(254, 252, 250));
.deepEqual(
assertInt16Array.from(typedArray).map(x => x * 2),
Int16Array.of(254, 252, 250)); // OK
.deepEqual(
assertInt16Array.from(typedArray.map(x => x * 2)),
Int16Array.of(-2, -4, -6)); // wrong
如果我们从类型化数组转换为精度较低的类型化数组,则通过 .from()
进行映射会产生正确的结果。否则,我们必须先映射再转换。
.deepEqual(
assertInt8Array.from(Int16Array.of(254, 252, 250), x => x / 2),
Int8Array.of(127, 126, 125));
.deepEqual(
assertInt8Array.from(Int16Array.of(254, 252, 250).map(x => x / 2)),
Int8Array.of(127, 126, 125)); // OK
.deepEqual(
assertInt8Array.from(Int16Array.of(254, 252, 250)).map(x => x / 2),
Int8Array.of(-1, -2, -3)); // wrong
问题是,如果我们通过 .map()
进行映射,则输入类型和输出类型相同。相反,.from()
从任意输入类型到您通过其接收器指定的输出类型。
类型化数组是可迭代的。这意味着您可以使用 for-of
循环和其他基于迭代的机制
const ui8 = Uint8Array.of(0, 1, 2);
for (const byte of ui8) {
console.log(byte);
}// Output:
// 0
// 1
// 2
ArrayBuffer 和 DataView 不可迭代。
类型化数组与普通数组非常相似:它们具有 .length
,可以通过方括号运算符 []
访问元素,并且它们具有大多数标准数组方法。它们与普通数组的不同之处在于以下几点
类型化数组拥有缓冲区。类型化数组 ta
的元素并不存储在 ta
中,而是存储在一个关联的 ArrayBuffer 中,可以通过 ta.buffer
访问。
const ta = new Uint16Array(2); // 2 elements
.deepEqual(
assert.buffer, new ArrayBuffer(4)); // 4 bytes ta
类型化数组初始化为零。
new Array(4)
创建一个没有任何元素的普通数组。它只有四个空位(小于 .length
且没有关联元素的索引)。new Uint8Array(4)
创建一个类型化数组,其四个元素均为 0。.deepEqual(new Uint8Array(4), Uint8Array.of(0, 0, 0, 0)); assert
类型化数组的所有元素都具有相同的类型。
设置元素会将值转换为该类型。
const ta = new Uint8Array(1);
0] = 257;
ta[.equal(ta[0], 1); // 257 % 256 (overflow)
assert
0] = '2';
ta[.equal(ta[0], 2); assert
获取元素会返回数字或大整数。
const ta = new Uint8Array(1);
.equal(ta[0], 0);
assert.equal(typeof ta[0], 'number'); assert
类型化数组的 .length
源自其 ArrayBuffer,并且永远不会更改(除非切换到不同的 ArrayBuffer)。
普通数组可以有空位;类型化数组则不能。
要将普通数组转换为类型化数组,可以将其传递给类型化数组构造函数(它接受类数组对象和类型化数组)或 «ElementType»Array.from()
(它接受可迭代对象和类数组对象)。例如:
const ta1 = new Uint8Array([0, 1, 2]);
const ta2 = Uint8Array.from([0, 1, 2]);
.deepEqual(ta1, ta2); assert
要将类型化数组转换为普通数组,可以使用 Array.from()
或展开运算符(因为类型化数组是可迭代的)。
.deepEqual(
assert...Uint8Array.of(0, 1, 2)], [0, 1, 2]
[;
).deepEqual(
assertArray.from(Uint8Array.of(0, 1, 2)), [0, 1, 2]
; )
与普通数组不同,类型化数组没有 .concat()
方法。解决方法是使用其重载方法 .set()
。
.set(typedArray: TypedArray, offset=0): void
.set(arrayLike: ArrayLike<number>, offset=0): void
它将现有的 typedArray
或 arrayLike
复制到接收器中,索引为 offset
。TypedArray
是所有具体类型化数组类的虚拟抽象超类。
以下函数使用该方法将零个或多个类型化数组(或类数组对象)复制到 resultConstructor
的实例中。
function concatenate(resultConstructor, ...arrays) {
let totalLength = 0;
for (const arr of arrays) {
+= arr.length;
totalLength
}const result = new resultConstructor(totalLength);
let offset = 0;
for (const arr of arrays) {
.set(arr, offset);
result+= arr.length;
offset
}return result;
}.deepEqual(
assertconcatenate(Uint8Array, Uint8Array.of(1, 2), [3, 4]),
Uint8Array.of(1, 2, 3, 4));
在准备 ArrayBuffer、类型化数组和 DataView 的快速参考之前,我们需要了解索引和偏移量之间的区别。
方括号运算符 [ ]
的索引:只能使用非负索引(从 0 开始)。
在普通数组中,写入负索引会创建属性。
const arr = [6, 7];
-1] = 5;
arr[.deepEqual(
assertObject.keys(arr), ['0', '1', '-1']);
在类型化数组中,写入负索引会被忽略。
const tarr = Uint8Array.of(6, 7);
-1] = 5;
tarr[.deepEqual(
assertObject.keys(tarr), ['0', '1']);
ArrayBuffer、类型化数组和 DataView 方法的索引:每个索引都可以为负。如果是负数,则将其添加到实体的长度以生成实际索引。因此,-1
指的是最后一个元素,-2
指的是倒数第二个元素,依此类推。普通数组的方法的工作方式相同。
const ui8 = Uint8Array.of(0, 1, 2);
.deepEqual(ui8.slice(-1), Uint8Array.of(2)); assert
传递给类型化数组和 DataView 方法的偏移量:必须是非负数,例如:
const dataView = new DataView(new ArrayBuffer(4));
.throws(
assert=> dataView.getUint8(-1),
()
{name: 'RangeError',
message: 'Offset is outside the bounds of the DataView',
; })
参数是索引还是偏移量只能通过查看文档来确定;没有简单的规则。
ArrayBuffer 存储二进制数据,这些数据旨在通过类型化数组和 DataView 进行访问。
new ArrayBuffer()
构造函数的类型签名为:
new ArrayBuffer(length: number)
通过 new
调用此构造函数会创建一个容量为 length
字节的实例。这些字节最初都为 0。
无法更改 ArrayBuffer 的长度;只能创建长度不同的新 ArrayBuffer。
ArrayBuffer
的静态方法ArrayBuffer.isView(arg: any)
如果 arg
是一个对象并且是 ArrayBuffer 的视图(即,如果它是一个类型化数组或 DataView),则返回 true
。
ArrayBuffer.prototype
的属性get .byteLength(): number
以字节为单位返回此 ArrayBuffer 的容量。
.slice(startIndex: number, endIndex=this.byteLength)
创建一个新的 ArrayBuffer,其中包含此 ArrayBuffer 中索引大于或等于 startIndex
且小于 endIndex
的字节。start
和 endIndex
可以为负数(请参阅 §32.4 “快速参考:索引与偏移量”)。
各种类型化数组对象的属性分两步介绍:
TypedArray
:首先,我们看一下所有类型化数组类的抽象超类(如本章开头的类图 所示)。我将该超类称为 TypedArray
,但它不能从 JavaScript 直接访问。TypedArray.prototype
包含类型化数组的所有方法。«ElementType»Array
:具体的类型化数组类称为 Uint8Array
、Int16Array
、Float32Array
等。这些是通过 new
、.of
和 .from()
使用的类。TypedArray<T>
的静态方法两个静态 TypedArray
方法都由其子类(Uint8Array
等)继承。TypedArray
是抽象的。因此,始终通过子类使用这些方法,这些子类是具体的并且可以具有直接实例。
.from<S>(source: Iterable<S>|ArrayLike<S>, mapfn?: S => T, thisArg?: any) : instanceof this
将可迭代对象(包括数组和类型化数组)或 类数组对象 转换为 this
的实例(instanceof this
是我为了表达这一事实而发明的)。
.deepEqual(
assertUint16Array.from([0, 1, 2]),
Uint16Array.of(0, 1, 2));
可选的 mapfn
允许在 source
的元素成为结果的元素之前对其进行转换。
.deepEqual(
assertInt16Array.from(Int8Array.of(127, 126, 125), x => x * 2),
Int16Array.of(254, 252, 250));
.of(...items: bigint[]): instanceof this
(BigInt64Array
, BigUint64Array
)
.of(...items: number[]): instanceof this
(所有其他类型化数组)
创建一个新的 this
实例,其元素为 items
(强制转换为元素类型)。
.deepEqual(
assertInt16Array.of(-1234, 5, 67),
new Int16Array([-1234, 5, 67]) );
TypedArray<T>.prototype
的属性类型化数组方法接受的索引可以为负数(它们的工作方式类似于传统的数组方法)。偏移量必须是非负数。有关详细信息,请参阅 §32.4 “快速参考:索引与偏移量”。
以下属性是类型化数组特有的;普通数组没有这些属性。
get .buffer(): ArrayBuffer
返回支持此类型化数组的缓冲区。
get .length(): number
以元素数量返回此类型化数组缓冲区的长度。
get .byteLength(): number
以字节为单位返回此类型化数组缓冲区的大小。
get .byteOffset(): number
返回此类型化数组在其 ArrayBuffer 内“开始”处的偏移量。
.set(typedArray: TypedArray, offset=0): void
.set(arrayLike: ArrayLike<bigint>, offset=0): void
(BigInt64Array
, BigUint64Array
)
.set(arrayLike: ArrayLike<number>, offset=0): void
(所有其他类型化数组)
将第一个参数的所有元素复制到此类型化数组。参数中索引为 0 的元素将写入此类型化数组的索引 offset
处(依此类推)。有关类数组对象的更多信息,请参阅 §31.5 “类数组对象”。
.subarray(startIndex=0, endIndex=this.length): TypedArray<T>
返回一个新的类型化数组,该数组与当前类型化数组具有相同的缓冲区,但范围(通常)更小。如果 startIndex
为非负数,则结果类型化数组的第一个元素为 this[startIndex]
,第二个元素为 this[startIndex+1]
(依此类推)。如果 startIndex
为负数,则会进行相应的转换。
以下方法与普通数组的方法基本相同:
.at(index: number): T | undefined
[R, ES2022].copyWithin(target: number, start: number, end=this.length): this
[W, ES6].entries(): Iterable<[number, T]>
[R, ES6].every(callback: (value: T, index: number, array: TypedArray<T>) => boolean, thisArg?: any): boolean
[R, ES6].fill(value: T, start=0, end=this.length): this
[W, ES6].filter(callback: (value: T, index: number, array: TypedArray<T>) => any, thisArg?: any): T[]
[R, ES6].find(predicate: (value: T, index: number, obj: T[]) => boolean, thisArg?: any): T | undefined
[R, ES6].findIndex(predicate: (value: T, index: number, obj: T[]) => boolean, thisArg?: any): number
[R, ES6].forEach(callback: (value: T, index: number, array: TypedArray<T>) => void, thisArg?: any): void
[R, ES6].includes(searchElement: T, fromIndex=0): boolean
[R, ES2016].indexOf(searchElement: T, fromIndex=0): number
[R, ES6].join(separator = ','): string
[R, ES6].keys(): Iterable<number>
[R, ES6].lastIndexOf(searchElement: T, fromIndex=this.length-1): number
[R, ES6].map<U>(mapFunc: (value: T, index: number, array: TypedArray<T>) => U, thisArg?: any): U[]
[R, ES6].reduce<U>(callback: (accumulator: U, element: T, index: number, array: T[]) => U, init?: U): U
[R, ES6].reduceRight<U>(callback: (accumulator: U, element: T, index: number, array: T[]) => U, init?: U): U
[R, ES6].reverse(): this
[W, ES6].slice(start=0, end=this.length): T[]
[R, ES6].some(callback: (value: T, index: number, array: TypedArray<T>) => boolean, thisArg?: any): boolean
[R, ES6].sort(compareFunc?: (a: T, b: T) => number): this
[W, ES6].toString(): string
[R, ES6].values(): Iterable<T>
[R, ES6]有关这些方法如何工作的详细信息,请参阅 §31.13.3 “Array.prototype
的方法”。
new «ElementType»Array()
每个类型化数组构造函数的名称都遵循模式 «ElementType»Array
,其中 «ElementType»
是开头表格中的元素类型之一。这意味着类型化数组有 11 个构造函数:
Float32Array
、Float64Array
Int8Array
、Int16Array
、Int32Array
、BigInt64Array
Uint8Array
、Uint8ClampedArray
、Uint16Array
、Uint32Array
、BigUint64Array
每个构造函数都有四个重载版本,它的行为取决于它接收的参数数量及其类型:
new «ElementType»Array(buffer: ArrayBuffer, byteOffset=0, length=0)
创建一个新的 «ElementType»Array
,其缓冲区为 buffer
。它从给定的 byteOffset
处开始访问缓冲区,并将具有给定的 length
。请注意,length
计算的是类型化数组的元素数量(每个元素 1-8 个字节),而不是字节数。
new «ElementType»Array(length=0)
创建一个具有给定 length
和相应缓冲区的新 «ElementType»Array
。缓冲区的大小(以字节为单位)为:
* «ElementType»Array.BYTES_PER_ELEMENT length
new «ElementType»Array(source: TypedArray)
创建一个新的 «ElementType»Array
实例,其元素的值与 source
的元素相同,但强制转换为 ElementType
。
new «ElementType»Array(source: ArrayLike<bigint>)
(BigInt64Array
, BigUint64Array
)
new «ElementType»Array(source: ArrayLike<number>)
(所有其他类型化数组)
创建一个新的 «ElementType»Array
实例,其元素的值与 source
的元素相同,但强制转换为 ElementType
。有关类数组对象的更多信息,请参阅 §31.5 “类数组对象”。
«ElementType»Array
的静态属性«ElementType»Array.BYTES_PER_ELEMENT: number
计算存储单个元素所需的字节数。
> Uint8Array.BYTES_PER_ELEMENT1
> Int16Array.BYTES_PER_ELEMENT2
> Float64Array.BYTES_PER_ELEMENT8
«ElementType»Array.prototype
的属性.BYTES_PER_ELEMENT: number
与 «ElementType»Array.BYTES_PER_ELEMENT
相同。
new DataView()
new DataView(buffer: ArrayBuffer, byteOffset=0, byteLength=buffer.byteLength-byteOffset)
buffer
中。默认情况下,新的 DataView 可以访问 buffer
的所有内容。最后两个参数允许更改此行为。DataView.prototype
的属性在本节的其余部分中,«ElementType»
指的是以下任意一项:
Int8
、Int16
、Int32
、BigInt64
Uint8
、Uint16
、Uint32
、BigUint64
Float32
、Float64
以下是 DataView.prototype
的属性:
get .buffer(): ArrayBuffer
返回此 DataView 的 ArrayBuffer。
get .byteLength(): number
返回此 DataView 可以访问的字节数。
get .byteOffset(): number
返回此 DataView 开始访问其缓冲区中的字节的偏移量。
.get«ElementType»(byteOffset: number, littleEndian=false): bigint
(BigInt64
, BigUint64
)
.get«ElementType»(byteOffset: number, littleEndian=false): number
(所有其他元素类型)
从此 DataView 的缓冲区中读取一个值。
.set«ElementType»(byteOffset: number, value: bigint, littleEndian=false): void
(BigInt64
, BigUint64
)
.set«ElementType»(byteOffset: number, value: number, littleEndian=false): void
(所有其他元素类型)
将 value
写入此 DataView 的缓冲区。