ArrayBuffer
constructorArrayBuffer
methodsArrayBuffer.prototype
propertiesTypedArray
methodsTypedArray.prototype
properties«ElementType»Array
constructor«ElementType»Array
properties«ElementType»Array.prototype
propertiesDataView
constructorDataView.prototype
propertiesXMLHttpRequest
Typed Arrays are an ECMAScript 6 API for handling binary data.
Code example:
Instances of ArrayBuffer
store the binary data to be processed. Two kinds of views are used to access the data:
Uint8Array
, Int16Array
, Float32Array
, etc.) interpret the ArrayBuffer as an indexed sequence of elements of a single type.DataView
let you access data as elements of several types (Uint8
, Int16
, Float32
, etc.), at any byte offset inside an ArrayBuffer.The following browser APIs support Typed Arrays (details are mentioned in a dedicated section):
Much data one encounters on the web is text: JSON files, HTML files, CSS files, JavaScript code, etc. For handling such data, JavaScript’s built-in string data type works well. However, until a few years ago, JavaScript was ill-equipped to handle binary data. On 8 February 2011, the Typed Array Specification 1.0 standardized facilities for handling binary data. By now, Typed Arrays are well supported by various engines. With ECMAScript 6, they became part of the core language and gained many methods in the process that were previously only available for Arrays (map()
, filter()
, etc.).
The main uses cases for Typed Arrays are:
Two kinds of objects work together in the Typed Array API:
ArrayBuffer
hold the binary data.Uint8Array
, Float64Array
, etc.) works much like a normal Array, but only allows a single type for its elements and doesn’t have holes.DataView
lets you access data at any byte offset in the buffer, and interprets that data as one of several types (Uint8
, Float64
, etc.).This is a diagram of the structure of the Typed Array API (notable: all Typed Arrays have a common superclass):
The following element types are supported by the API:
Element type | Bytes | Description | C type |
---|---|---|---|
Int8 | 1 | 8-bit signed integer | signed char |
Uint8 | 1 | 8-bit unsigned integer | unsigned char |
Uint8C | 1 | 8-bit unsigned integer (clamped conversion) | unsigned char |
Int16 | 2 | 16-bit signed integer | short |
Uint16 | 2 | 16-bit unsigned integer | unsigned short |
Int32 | 4 | 32-bit signed integer | int |
Uint32 | 4 | 32-bit unsigned integer | unsigned int |
Float32 | 4 | 32-bit floating point | float |
Float64 | 8 | 64-bit floating point | double |
The element type Uint8C
is special: it is not supported by DataView
and only exists to enable Uint8ClampedArray
. This Typed Array is used by the canvas
element (where it replaces CanvasPixelArray
). The only difference between Uint8C
and Uint8
is how overflow and underflow are handled (as explained in the next section). It is recommended to avoid the former – quoting Brendan Eich:
Just to be super-clear (and I was around when it was born),
Uint8ClampedArray
is totally a historical artifact (of the HTML5 canvas element). Avoid unless you really are doing canvas-y things.
Normally, when a value is out of the range of the element type, modulo arithmetic is used to convert it to a value within range. For signed and unsigned integers that means that:
Modulo conversion for unsigned 8-bit integers:
Modulo conversion for signed 8-bit integers:
Clamped conversion is different:
Whenever a type (such as Uint16
) is stored as multiple bytes, endianness matters:
Uint16
value 0xABCD is stored as two bytes – first 0xAB, then 0xCD.Uint16
value 0xABCD is stored as two bytes – first 0xCD, then 0xAB.Endianness tends to be fixed per CPU architecture and consistent across native APIs. Typed Arrays are used to communicate with those APIs, which is why their endianness follows the endianness of the platform and can’t be changed.
On the other hand, the endianness of protocols and binary files varies and is fixed across platforms. Therefore, we must be able to access data with either endianness. DataViews serve this use case and let you specify endianness when you get or set a value.
Quoting Wikipedia on Endianness:
You can use the following function to determine the endianness of a platform.
There are also platforms that arrange words (pairs of bytes) with a different endianness than bytes inside words. That is called mixed endianness. Should you want to support such a platform then it is easy to extend the previous code.
With the bracket operator [ ]
, you can only use non-negative indices (starting at 0). The methods of ArrayBuffers, Typed Arrays and DataViews work differently: every index can be negative. If it is, it counts backwards from the length. In other words, it is added to the length to produce a normal index. Therefore -1
refers to the last element, -2
to the second-last, etc. Methods of normal Arrays work the same way.
Offsets, on the other hand, must be non-negative. If, for example, you pass -1
to:
then you get a RangeError
.
ArrayBuffers store the data, views (Typed Arrays and DataViews) let you read and change it. In order to create a DataView, you need to provide its constructor with an ArrayBuffer. Typed Array constructors can optionally create an ArrayBuffer for you.
ArrayBuffer
constructor The signature of the constructor is:
Invoking this constructor via new
creates an instance whose capacity is length
bytes. Each of those bytes is initially 0.
ArrayBuffer
methods ArrayBuffer.isView(arg)
true
if arg
is an object and a view for an ArrayBuffer. Only Typed Arrays and DataViews have the required internal slot [[ViewedArrayBuffer]]
. That means that this check is roughly equivalent to checking whether arg
is an instance of a Typed Array or of DataView
.ArrayBuffer.prototype
properties get ArrayBuffer.prototype.byteLength
ArrayBuffer.prototype.slice(start, end)
start
and less than end
. start
and end
can be negative (see Sect. “Negative indices”).The various kinds of Typed Array are only different w.r.t. the type of their elements:
Int8Array
, Uint8Array
, Uint8ClampedArray
, Int16Array
, Uint16Array
, Int32Array
, Uint32Array
Float32Array
, Float64Array
Typed Arrays are much like normal Arrays: they have a length
, elements can be accessed via the bracket operator [ ]
and they have all of the standard Array methods. They differ from Arrays in the following ways:
arr.length
) that have no associated element), Typed Arrays can’t.new Array(10)
creates a normal Array without any elements (it only has holes).new Uint8Array(10)
creates a Typed Array whose 10 elements are all 0.ta
are not stored in ta
, they are stored in an associated ArrayBuffer that can be accessed via ta.buffer
.Typed Arrays implement a method whose key is Symbol.iterator
and are therefore iterable (consult chapter “Iterables and iterators” for more information). That means that you can use the for-of
loop and similar mechanisms in ES6:
ArrayBuffers and DataViews are not iterable.
To convert a normal Array to a Typed Array, you make it the parameter of a Typed Array constructor. For example:
The classic way to convert a Typed Array to an Array is to invoke Array.prototype.slice
on it. This trick works for all Array-like objects (such as arguments
) and Typed Arrays are Array-like.
In ES6, you can use the spread operator (...
), because Typed Arrays are iterable:
Another ES6 alternative is Array.from()
, which works with either iterables or Array-like objects:
Some methods create new instances that are similar to this
. The species pattern lets you configure what constructor should be used to do so. For example, if you create a subclass MyArray
of Array
then the default is that map()
creates instances of MyArray
. If you want it to create instances of Array
, you can use the species pattern to make that happen. Details are explained in Sect “The species pattern” in the chapter on classes.
ArrayBuffers use the species pattern in the following locations:
ArrayBuffer.prototype.slice()
Typed Arrays use the species pattern in the following locations:
TypedArray<T>.prototype.filter()
TypedArray<T>.prototype.map()
TypedArray<T>.prototype.slice()
TypedArray<T>.prototype.subarray()
DataViews don’t use the species pattern.
As you could see in the diagram at the beginning of this chapter, all Typed Array classes (Uint8Array
etc.) have a common superclass. I’m calling that superclass TypedArray
, but it is not directly accessible from JavaScript (the ES6 specification calls it the intrinsic object %TypedArray%
). TypedArray.prototype
houses all methods of Typed Arrays.
TypedArray
methods Both static TypedArray
methods are inherited by its subclasses (Uint8Array
etc.).
TypedArray.of()
This method has the signature:
It creates a new Typed Array that is an instance of this
(the class on which of()
was invoked). The elements of that instance are the parameters of of()
.
You can think of of()
as a custom literal for Typed Arrays:
TypedArray.from()
This method has the signature:
It converts the iterable source
into an instance of this
(a Typed Array).
For example, normal Arrays are iterable and can be converted with this method:
Typed Arrays are iterable, too:
The optional mapfn
lets you transform the elements of source
before they become elements of the result. Why perform the two steps mapping and conversion in one go? Compared to performing the first step separately, via source.map()
, there are two advantages:
To illustrate the second advantage, let’s use map()
to double the elements of a Typed Array:
As you can see, the values overflow and are coerced into the Int8
range of values. If map via from()
, you can choose the type of the result so that values don’t overflow:
According to Allen Wirfs-Brock, mapping between Typed Arrays was what motivated the mapfn
parameter of from()
.
TypedArray.prototype
properties Indices accepted by Typed Array methods can be negative (they work like traditional Array methods that way). Offsets must be non-negative. For details, see Sect. “Negative indices”.
The following properties are specific to Typed Arrays, normal Arrays don’t have them:
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
to this Typed Array. The element at index 0 of arrayOrTypedArray
is written to index offset
of this Typed Array (etc.).
arrayOrTypedArray
is a normal Array, its elements are converted to numbers who are then converted to the element type T
of this Typed Array.arrayOrTypedArray
is a Typed Array then each of its elements is converted directly to the appropriate type for this Typed Array. If both Typed Arrays have the same element type then faster, byte-wise copying is used.TypedArray<T>.prototype.subarray(begin=0, end=this.length) : TypedArray<T>
begin
is non-negative then the first element of the resulting Typed Array is this[begin]
, the second this[begin+1]
(etc.). If begin
in negative, it is converted appropriately.The following methods are basically the same as the methods of normal Arrays:
TypedArray<T>.prototype.copyWithin(target : number, start : number, end = this.length) : This
start
(including) and end
(excluding) to indices starting at target
. If the ranges overlap and the former range comes first then elements are copied in reverse order to avoid overwriting source elements before they are copied.TypedArray<T>.prototype.entries() : Iterable<[number,T]>
TypedArray<T>.prototype.every(callbackfn, thisArg?)
true
if callbackfn
returns true
for every element of this Typed Array. Otherwise, it returns false
. every()
stops processing the first time callbackfn
returns false
.TypedArray<T>.prototype.fill(value, start=0, end=this.length) : void
start
to end
to value
.TypedArray<T>.prototype.filter(callbackfn, thisArg?) : TypedArray<T>
callbackfn
returns true
. In general, the result is shorter than this Typed Array.TypedArray<T>.prototype.find(predicate : T => boolean, thisArg?) : T
predicate
returns true
.TypedArray<T>.prototype.findIndex(predicate : T => boolean, thisArg?) : number
predicate
returns true
.TypedArray<T>.prototype.forEach(callbackfn, thisArg?) : void
callbackfn
for each element.TypedArray<T>.prototype.indexOf(searchElement, fromIndex=0) : number
searchElement
. The search starts at fromIndex
.TypedArray<T>.prototype.join(separator : string = ',') : string
separator
.TypedArray<T>.prototype.keys() : Iterable<number>
TypedArray<T>.prototype.lastIndexOf(searchElement, fromIndex?) : number
searchElement
. The search starts at fromIndex
, backwards.get TypedArray<T>.prototype.length : number
TypedArray<T>.prototype.map(callbackfn, thisArg?) : TypedArray<T>
callbackfn
to the corresponding element of this Typed Array.TypedArray<T>.prototype.reduce(callbackfn : (previousValue : any, currentElement : T, currentIndex : number, array : TypedArray<T>) => any, initialValue?) : any
callbackfn
is fed one element at a time, together with the result that was computed so far and computes a new result. Elements are visited from left to right.TypedArray<T>.prototype.reduceRight(callbackfn : (previousValue : any, currentElement : T, currentIndex : number, array : TypedArray<T>) => any, initialValue?) : any
callbackfn
is fed one element at a time, together with the result that was computed so far and computes a new result. Elements are visited from right to left.TypedArray<T>.prototype.reverse() : This
this
.TypedArray<T>.prototype.slice(start=0, end=this.length) : TypedArray<T>
start
(including) and end
(excluding).TypedArray<T>.prototype.some(callbackfn, thisArg?)
true
if callbackfn
returns true
for at least one element of this Typed Array. Otherwise, it returns false
. some()
stops processing the first time callbackfn
returns true
.TypedArray<T>.prototype.sort(comparefn? : (number, number) => number)
comparefn
. If comparefn
is missing, sorting is done ascendingly, by comparing via the less-than operator (<
).TypedArray<T>.prototype.toLocaleString(reserved1?, reserved2?)
TypedArray<T>.prototype.toString()
TypedArray<T>.prototype.values() : Iterable<T>
Due to all of these methods being available for Arrays, you can consult the following two sources to find out more about how they work:
copyWithin
, entries
, fill
, find
, findIndex
, keys
, values
.Note that while normal Array methods are generic (any Array-like this
is OK), the methods listed in this section are not (this
must be a Typed Array).
«ElementType»Array
constructor Each Typed Array constructor has a name that follows the pattern «ElementType»Array
, where «ElementType»
is one of the element types in the table at the beginning. That means that there are 9 constructors for Typed Arrays: Int8Array
, Uint8Array
, Uint8ClampedArray
(element type Uint8C
), Int16Array
, Uint16Array
, Int32Array
, Uint32Array
, Float32Array
, Float64Array
.
Each constructor has five overloaded versions – it behaves differently depending on how many arguments it receives and what their types are:
«ElementType»Array(buffer, byteOffset=0, length?)
buffer
. It starts accessing the buffer at the given byteOffset
and will have the given length
. Note that length
counts elements of the Typed Array (with 1–4 bytes each), not bytes.«ElementType»Array(length)
length
and the appropriate buffer (whose size in bytes is length * «ElementType»Array.BYTES_PER_ELEMENT
).«ElementType»Array()
length
is 0. It also creates an associated empty ArrayBuffer.«ElementType»Array(typedArray)
typedArray
. Values that are too large or small are converted appropriately.«ElementType»Array(arrayLikeObject)
arrayLikeObject
like an Array and creates a new TypedArray that has the same length and elements. Values that are too large or small are converted appropriately.The following code shows three different ways of creating the same Typed Array:
«ElementType»Array
properties «ElementType»Array.BYTES_PER_ELEMENT
«ElementType»Array.prototype
properties «ElementType»Array.prototype.BYTES_PER_ELEMENT
«ElementType»Array.BYTES_PER_ELEMENT
.Typed Arrays don’t have a method concat()
, like normal Arrays do. The work-around is to use the method
That method copies an existing Typed Array (or normal Array) into typedArray
at index offset
. Then you only have to make sure that typedArray
is big enough to hold all (Typed) Arrays you want to concatenate:
DataView
constructor DataView(buffer, byteOffset=0, byteLength=buffer.byteLength-byteOffset)
buffer
. By default, the new DataView can access all of buffer
, the last two parameters allow you to change that.DataView.prototype
properties get DataView.prototype.buffer
get DataView.prototype.byteLength
get DataView.prototype.byteOffset
DataView.prototype.get«ElementType»(byteOffset, littleEndian=false)
«ElementType»
can be: Float32
, Float64
, Int8
, Int16
, Int32
, Uint8
, Uint16
, Uint32
DataView.prototype.set«ElementType»(byteOffset, value, littleEndian=false)
value
to the buffer of this DataView.
«ElementType»
can be: Float32
, Float64
, Int8
, Int16
, Int32
, Uint8
, Uint16
, Uint32
Typed Arrays have been around for a while, so there are quite a few browser APIs that support them.
The file API lets you access local files. The following code demonstrates how to get the bytes of a submitted local file in an ArrayBuffer.
XMLHttpRequest
In newer versions of the XMLHttpRequest
API, you can have the results delivered in an ArrayBuffer:
Similarly to XMLHttpRequest
, the Fetch API lets you request resources. But it is based on Promises, which makes it more convenient to use. The following code demonstrates how to download the content pointed to by url
as an ArrayBuffer:
Quoting the HTML5 specification:
The
canvas
element provides scripts with a resolution-dependent bitmap canvas, which can be used for rendering graphs, game graphics, art, or other visual images on the fly.
The 2D Context of canvas
lets you retrieve the bitmap data as an instance of Uint8ClampedArray
:
WebSockets let you send and receive binary data via ArrayBuffers:
<audio>
and <video>
. The Media Source Extensions API enables you to create streams to be played via those elements. You can add binary data to such streams via ArrayBuffers, Typed Arrays or DataViews.postMessage()
, either the message (which will be cloned) or the transferable objects can contain ArrayBuffers.postMessage()
.The example is a web pages that lets you upload a JPEG file and parses its structure to determine the height and the width of the image and more.
A JPEG file is a sequence of segments (typed data). Each segment starts with the following four bytes:
JPEG files are big-endian on all platforms. Therefore, this example demonstrates how important it is that we can specify endianness when using DataViews.
The following function processArrayBuffer()
is an abridged version of the actual code; I’ve removed a few error checks to reduce clutter. processArrayBuffer()
receives an ArrayBuffer with the contents of the submitted JPEG file and iterates over its segments.
This code uses the following helper functions (that are not shown here):
enforceValue()
throws an error if the expected value (first parameter) doesn’t match the actual value (second parameter).logInfo()
and logError()
display messages on the page.hex()
turns a number into a string with two hexadecimal digits.decodeSOF0()
parses the segment SOF0:
More information on the structure of JPEG files:
Much of the Typed Array API is implemented by all modern JavaScript engines, but several features are new to ECMAScript 6:
TypedArray<T>.from()
, TypedArray<T>.of()
TypedArray<T>.prototype.map()
etc.TypedArray<T>
is the superclass of all Typed Array classesIt may take a while until these are available everywhere. As usual, kangax’ “ES6 compatibility table” describes the status quo.