ArrayBuffer
构造函数ArrayBuffer
方法ArrayBuffer.prototype
属性TypedArray
方法TypedArray.prototype
属性«ElementType»Array
构造函数«ElementType»Array
属性«ElementType»Array.prototype
属性DataView
构造函数DataView.prototype
属性XMLHttpRequest
类型化数组是 ECMAScript 6 中用于处理二进制数据的 API。
代码示例
ArrayBuffer
的实例存储要处理的二进制数据。两种 视图 用于访问数据
Uint8Array
、Int16Array
、Float32Array
等)将 ArrayBuffer 解释为单一类型元素的索引序列。DataView
的实例允许您以多种类型(Uint8
、Int16
、Float32
等)访问 ArrayBuffer 内任意字节偏移量处的数据。以下浏览器 API 支持类型化数组(详细信息在专门的章节中提到)
我们在网络上遇到的很多数据都是文本:JSON 文件、HTML 文件、CSS 文件、JavaScript 代码等等。对于处理此类数据,JavaScript 内置的字符串数据类型非常有效。然而,直到几年前,JavaScript 还很不擅长处理二进制数据。2011 年 2 月 8 日,类型化数组规范 1.0 标准化了处理二进制数据的工具。到目前为止,类型化数组已经得到 各种引擎的良好支持。随着 ECMAScript 6 的出现,它们成为了核心语言的一部分,并在此过程中获得了许多以前只能用于数组的方法(map()
、filter()
等)。
类型化数组的主要用例是
类型化数组 API 中有两种对象协同工作
ArrayBuffer
的实例保存二进制数据。Uint8Array
、Float64Array
等)的实例的工作方式与普通数组非常相似,但只允许其元素具有单一类型,并且没有空洞。DataView
的实例允许您访问缓冲区中任意字节偏移量处的数据,并将该数据解释为几种类型之一(Uint8
、Float64
等)。这是类型化数组 API 结构的示意图(值得注意的是:所有类型化数组都有一个共同的超类)
API 支持以下元素类型
元素类型 | 字节数 | 描述 | C 类型 |
---|---|---|---|
Int8 | 1 | 8 位有符号整数 | signed char |
Uint8 | 1 | 8 位无符号整数 | unsigned char |
Uint8C | 1 | 8 位无符号整数(钳制转换) | unsigned char |
Int16 | 2 | 16 位有符号整数 | short |
Uint16 | 2 | 16 位无符号整数 | unsigned short |
Int32 | 4 | 32 位有符号整数 | int |
Uint32 | 4 | 32 位无符号整数 | unsigned int |
Float32 | 4 | 32 位浮点数 | float |
Float64 | 8 | 64 位浮点数 | double |
元素类型 Uint8C
很特殊:DataView
不支持它,它只存在于启用 Uint8ClampedArray
。canvas
元素使用此类型化数组(它取代了 CanvasPixelArray
)。Uint8C
和 Uint8
之间的唯一区别在于如何处理溢出和下溢(如下一节所述)。建议避免使用前者 – 引用 Brendan Eich 的话
需要明确的是(我当时就在场),
Uint8ClampedArray
完全 是一个历史遗留问题(来自 HTML5 canvas 元素)。除非您真的在做 canvas 相关的事情,否则请避免使用它。
通常,当一个值超出元素类型的范围时,将使用模运算将其转换为范围内的值。对于有符号和无符号整数,这意味着
无符号 8 位整数的模转换
有符号 8 位整数的模转换
钳制转换不同
每当一个类型(例如 Uint16
)存储为多个字节时,字节序就很重要
Uint16
值 0xABCD 存储为两个字节 – 首先是 0xAB,然后是 0xCD。Uint16
值 0xABCD 存储为两个字节 – 首先是 0xCD,然后是 0xAB。字节序往往在每个 CPU 架构上都是固定的,并且在原生 API 中保持一致。类型化数组用于与这些 API 通信,这就是为什么它们的字节序遵循平台的字节序并且不能更改的原因。
另一方面,协议和二进制文件的字节序各不相同,并且在不同平台上是固定的。因此,我们必须能够以任何一种字节序访问数据。DataViews 就用于这种情况,它允许您在获取或设置值时指定字节序。
您可以使用以下函数来确定平台的字节序。
还有一些平台以与字内字节不同的字节序排列 字(字节对)。这被称为混合字节序。如果您想支持这样的平台,那么很容易扩展前面的代码。
使用方括号运算符 [ ]
时,您只能使用非负索引(从 0 开始)。ArrayBuffers、类型化数组和 DataViews 的方法的工作方式不同:每个索引都可以为负数。如果是负数,则从长度开始倒数。换句话说,它被加到长度上以产生一个正常的索引。因此,-1
指的是最后一个元素,-2
指的是倒数第二个元素,等等。普通数组的方法的工作方式相同。
另一方面,偏移量必须是非负数。例如,如果您将 -1
传递给
那么您将得到一个 RangeError
。
ArrayBuffers 存储数据,视图(类型化数组和 DataViews)允许您读取和更改数据。为了创建 DataView,您需要为其构造函数提供一个 ArrayBuffer。类型化数组构造函数可以选择为您创建一个 ArrayBuffer。
ArrayBuffer
构造函数 构造函数的签名是
通过 new
调用此构造函数将创建一个容量为 length
字节的实例。这些字节最初都为 0。
ArrayBuffer
方法 ArrayBuffer.isView(arg)
arg
是一个对象并且是 ArrayBuffer 的视图,则返回 true
。只有类型化数组和 DataViews 具有所需的内部插槽 [[ViewedArrayBuffer]]
。这意味着此检查大致等同于检查 arg
是否是类型化数组或 DataView
的实例。ArrayBuffer.prototype
属性 get ArrayBuffer.prototype.byteLength
ArrayBuffer.prototype.slice(start, end)
start
且小于 end
的字节。start
和 end
可以为负数(请参阅“负索引”一节)。各种类型化数组的区别仅在于其元素的类型
Int8Array
、Uint8Array
、Uint8ClampedArray
、Int16Array
、Uint16Array
、Int32Array
、Uint32Array
Float32Array
、Float64Array
类型化数组与普通数组非常相似:它们都有 length
属性,可以通过方括号运算符 [ ]
访问元素,并且它们都具有所有标准的数组方法。它们与数组的不同之处在于以下几点
arr.length
) 范围内没有关联元素的索引),而类型化数组不能。new Array(10)
会创建一个没有元素的普通数组(它只有空位)。new Uint8Array(10)
会创建一个包含 10 个元素且所有元素均为 0 的类型化数组。ta
的元素不存储在 ta
中,而是存储在一个关联的 ArrayBuffer 中,可以通过 ta.buffer
访问该缓冲区。类型化数组实现了一个键为 Symbol.iterator
的方法,因此是可迭代的(有关更多信息,请参阅“可迭代对象和迭代器”一章)。这意味着您可以在 ES6 中使用 for-of
循环和类似机制
ArrayBuffers 和 DataViews 不可迭代。
要将普通数组转换为类型化数组,可以将其作为类型化数组构造函数的参数。例如
将类型化数组转换为数组的经典方法是在其上调用 Array.prototype.slice
。这个技巧适用于所有类数组对象(例如 arguments
),而类型化数组就是类数组。
在 ES6 中,您可以使用扩展运算符 (...
),因为类型化数组是可迭代的
另一个 ES6 替代方法是 Array.from()
,它适用于可迭代对象或类数组对象
有些方法会创建与 this
类似的新实例。Species 模式允许您配置应该使用哪个构造函数来执行此操作。例如,如果您创建了 Array
的子类 MyArray
,则默认情况下 map()
会创建 MyArray
的实例。如果您希望它创建 Array
的实例,则可以使用 Species 模式来实现。详细信息在类一章的“Species 模式”一节中进行了解释。
ArrayBuffers 在以下位置使用 Species 模式
ArrayBuffer.prototype.slice()
类型化数组在以下位置使用 Species 模式
TypedArray<T>.prototype.filter()
TypedArray<T>.prototype.map()
TypedArray<T>.prototype.slice()
TypedArray<T>.prototype.subarray()
DataViews 不使用 Species 模式。
正如您在本章开头的图表中所见,所有类型化数组类(Uint8Array
等)都有一个共同的超类。我将该超类称为 TypedArray
,但它不能从 JavaScript 直接访问(ES6 规范将其称为*内部对象 %TypedArray%
*)。TypedArray.prototype
包含类型化数组的所有方法。
TypedArray
方法 两个静态 TypedArray
方法都由其子类(Uint8Array
等)继承。
TypedArray.of()
此方法具有以下签名
它创建一个新的类型化数组,该数组是 this
(调用 of()
的类)的实例。该实例的元素是 of()
的参数。
您可以将 of()
视为类型化数组的自定义字面量
TypedArray.from()
此方法具有以下签名
它将可迭代对象 source
转换为 this
(类型化数组)的实例。
例如,普通数组是可迭代的,可以使用此方法进行转换
类型化数组也是可迭代的
可选的 mapfn
允许您在 source
的元素成为结果的元素之前对其进行转换。为什么要一步完成*映射*和*转换*这两个步骤?与通过 source.map()
单独执行第一步相比,有两个优点
为了说明第二个优点,让我们使用 map()
将类型化数组的元素加倍
如您所见,这些值溢出并被强制转换为 Int8
值范围。如果通过 from()
进行映射,则可以选择结果的类型,以便值不会溢出
据 Allen Wirfs-Brock 所说,类型化数组之间的映射是 from()
的 mapfn
参数的动机。
TypedArray.prototype
属性 类型化数组方法接受的索引可以是负数(它们的工作方式类似于传统的数组方法)。偏移量必须为非负数。有关详细信息,请参阅“负索引”一节。
以下属性是类型化数组特有的,普通数组没有这些属性
get TypedArray<T>.prototype.buffer : ArrayBuffer
get TypedArray<T>.prototype.byteLength : number
get TypedArray<T>.prototype.byteOffset : number
TypedArray<T>.prototype.set(arrayOrTypedArray, offset=0) : void
arrayOrTypedArray
的所有元素复制到此类型化数组。arrayOrTypedArray
中索引为 0 的元素将写入此类型化数组的索引 offset
处(依此类推)。arrayOrTypedArray
是普通数组,则其元素将转换为数字,然后转换为此类型化数组的元素类型 T
。arrayOrTypedArray
是类型化数组,则其每个元素都将直接转换为此类型化数组的相应类型。如果两个类型化数组具有相同的元素类型,则使用更快的按字节复制。TypedArray<T>.prototype.subarray(begin=0, end=this.length) : TypedArray<T>
begin
为非负数,则结果类型化数组的第一个元素为 this[begin]
,第二个元素为 this[begin+1]
(依此类推)。如果 begin
为负数,则会进行相应的转换。以下方法与普通数组的方法基本相同
TypedArray<T>.prototype.copyWithin(target : number, start : number, end = this.length) : This
start
(包括)和 end
(不包括)之间的元素复制到从 target
开始的索引处。如果范围重叠并且前一个范围在前,则元素将按相反顺序复制,以避免在复制源元素之前覆盖它们。TypedArray<T>.prototype.entries() : Iterable<[number,T]>
TypedArray<T>.prototype.every(callbackfn, thisArg?)
callbackfn
对此类型化数组的每个元素都返回 true
,则返回 true
。否则,它返回 false
。every()
在 callbackfn
首次返回 false
时停止处理。TypedArray<T>.prototype.fill(value, start=0, end=this.length) : void
start
到 end
的元素设置为 value
。TypedArray<T>.prototype.filter(callbackfn, thisArg?) : TypedArray<T>
callbackfn
返回 true
的每个元素。通常,结果比此类型化数组短。TypedArray<T>.prototype.find(predicate : T => boolean, thisArg?) : T
predicate
返回 true
的第一个元素。TypedArray<T>.prototype.findIndex(predicate : T => boolean, thisArg?) : number
predicate
返回 true
的第一个元素的索引。TypedArray<T>.prototype.forEach(callbackfn, thisArg?) : void
callbackfn
。TypedArray<T>.prototype.indexOf(searchElement, fromIndex=0) : number
searchElement
的第一个元素的索引。搜索从 fromIndex
开始。TypedArray<T>.prototype.join(separator : string = ',') : string
separator
分隔。TypedArray<T>.prototype.keys() : Iterable<number>
TypedArray<T>.prototype.lastIndexOf(searchElement, fromIndex?) : number
searchElement
的最后一个元素的索引。搜索从 fromIndex
开始,向后搜索。get TypedArray<T>.prototype.length : number
TypedArray<T>.prototype.map(callbackfn, thisArg?) : TypedArray<T>
callbackfn
应用于此类型化数组的相应元素的结果。TypedArray<T>.prototype.reduce(callbackfn : (previousValue : any, currentElement : T, currentIndex : number, array : TypedArray<T>) => any, initialValue?) : any
callbackfn
一次接收一个元素,以及到目前为止计算的结果,并计算新的结果。元素从左到右访问。TypedArray<T>.prototype.reduceRight(callbackfn : (previousValue : any, currentElement : T, currentIndex : number, array : TypedArray<T>) => any, initialValue?) : any
callbackfn
一次接收一个元素,以及到目前为止计算的结果,并计算新的结果。元素从右到左访问。TypedArray<T>.prototype.reverse() : This
this
。TypedArray<T>.prototype.slice(start=0, end=this.length) : TypedArray<T>
start
(包括)和 end
(不包括)之间的元素。TypedArray<T>.prototype.some(callbackfn, thisArg?)
callbackfn
对此类型化数组的至少一个元素返回 true
,则返回 true
。否则,它返回 false
。some()
在 callbackfn
首次返回 true
时停止处理。TypedArray<T>.prototype.sort(comparefn? : (number, number) => number)
comparefn
的指定对此类型化数组进行排序。如果缺少 comparefn
,则通过小于运算符 (<
) 进行比较,按升序排序。TypedArray<T>.prototype.toLocaleString(reserved1?, reserved2?)
TypedArray<T>.prototype.toString()
TypedArray<T>.prototype.values() : Iterable<T>
由于所有这些方法都可用于数组,因此您可以查阅以下两个来源以了解更多有关它们工作原理的信息
copyWithin
、entries
、fill
、find
、findIndex
、keys
、values
。请注意,虽然普通数组方法是通用的(任何类数组 this
都可以),但本节中列出的方法不是(this
必须是类型化数组)。
«ElementType»Array
构造函数 每个类型化数组构造函数的名称都遵循模式 «ElementType»Array
,其中 «ElementType»
是开头表格中的元素类型之一。这意味着类型化数组有 9 个构造函数:Int8Array
、Uint8Array
、Uint8ClampedArray
(元素类型 Uint8C
)、Int16Array
、Uint16Array
、Int32Array
、Uint32Array
、Float32Array
、Float64Array
。
每个构造函数都有五个重载版本 – 它的行为方式取决于它接收的参数数量及其类型。
«ElementType»Array(buffer, byteOffset=0, length?)
buffer
。它从给定的 byteOffset
开始访问缓冲区,并将具有给定的 length
。请注意,length
计算的是类型化数组的元素数量(每个元素 1-4 个字节),而不是字节数。«ElementType»Array(length)
length
和适当缓冲区的类型化数组(其大小(以字节为单位)为 length * «ElementType»Array.BYTES_PER_ELEMENT
)。«ElementType»Array()
length
为 0 的类型化数组。它还会创建一个关联的空 ArrayBuffer。«ElementType»Array(typedArray)
typedArray
具有相同长度和元素的新类型化数组。过大或过小的值将进行适当的转换。«ElementType»Array(arrayLikeObject)
arrayLikeObject
视为数组,并创建一个与之具有相同长度和元素的新类型化数组。过大或过小的值将进行适当的转换。以下代码展示了创建相同类型化数组的三种不同方法
«ElementType»Array
属性 «ElementType»Array.BYTES_PER_ELEMENT
«ElementType»Array.prototype
属性 «ElementType»Array.prototype.BYTES_PER_ELEMENT
«ElementType»Array.BYTES_PER_ELEMENT
相同。类型化数组没有像普通数组那样具有 concat()
方法。解决方法是使用方法
该方法将现有的类型化数组(或普通数组)复制到索引 offset
处的 typedArray
中。然后,您只需确保 typedArray
足够大以容纳要连接的所有(类型化)数组即可
DataView
构造函数 DataView(buffer, byteOffset=0, byteLength=buffer.byteLength-byteOffset)
buffer
中。默认情况下,新的 DataView 可以访问 buffer
的所有内容,最后两个参数允许您更改它。DataView.prototype
属性 get DataView.prototype.buffer
get DataView.prototype.byteLength
get DataView.prototype.byteOffset
DataView.prototype.get«ElementType»(byteOffset, littleEndian=false)
«ElementType»
可以是:Float32
、Float64
、Int8
、Int16
、Int32
、Uint8
、Uint16
、Uint32
DataView.prototype.set«ElementType»(byteOffset, value, littleEndian=false)
value
写入此 DataView 的缓冲区。«ElementType»
可以是:Float32
、Float64
、Int8
、Int16
、Int32
、Uint8
、Uint16
、Uint32
类型化数组已经存在一段时间了,因此有很多浏览器 API 支持它们。
文件 API 允许您访问本地文件。以下代码演示了如何获取 ArrayBuffer 中提交的本地文件的字节。
XMLHttpRequest
在较新版本的 XMLHttpRequest
API 中,您可以将结果以 ArrayBuffer 的形式传递
与 XMLHttpRequest
类似,Fetch API 允许您请求资源。但它基于 Promise,这使得它更易于使用。以下代码演示了如何将 url
指向的内容作为 ArrayBuffer 下载
canvas
元素为脚本提供了一个与分辨率相关的位图画布,可用于动态渲染图形、游戏图形、艺术品或其他视觉图像。
canvas
的 2D 上下文 允许您将位图数据检索为 Uint8ClampedArray
的实例
WebSockets 允许您通过 ArrayBuffers 发送和接收二进制数据
<audio>
和 <video>
。媒体源扩展 API 使您能够创建要通过这些元素播放的流。您可以通过 ArrayBuffers、类型化数组或 DataView 将二进制数据添加到此类流中。postMessage()
将数据发送到 Worker,则消息(将被克隆)或可转移对象可以包含 ArrayBuffers。postMessage()
方法。该示例是一个网页,允许您上传 JPEG 文件并解析其结构以确定图像的高度和宽度等。
JPEG 文件是一系列段(类型化数据)。每个段都以以下四个字节开头
JPEG 文件在所有平台上都是大端序的。因此,此示例演示了在使用 DataView 时指定字节序的重要性。
以下函数 processArrayBuffer()
是实际代码的简化版本;为了减少混乱,我删除了一些错误检查。processArrayBuffer()
接收一个包含提交的 JPEG 文件内容的 ArrayBuffer,并迭代其段。
此代码使用以下辅助函数(此处未显示)
enforceValue()
会引发错误。logInfo()
和 logError()
在页面上显示消息。hex()
将数字转换为具有两个十六进制数字的字符串。decodeSOF0()
解析段 SOF0
有关 JPEG 文件结构的更多信息
许多类型化数组 API 都是由所有现代 JavaScript 引擎实现的,但有几个功能是 ECMAScript 6 中新增的
TypedArray<T>.from()
、TypedArray<T>.of()
TypedArray<T>.prototype.map()
等TypedArray<T>
是所有类型化数组类的超类这些功能可能需要一段时间才能在所有地方都可用。像往常一样,kangax 的“ES6 兼容性表”描述了现状。