Why Typed Arrays exist
Typed Arrays appeared in browsers to solve a concrete problem: WebGL needed a fast way to exchange binary data with native graphics libraries. A typed array is a contiguous block of memory with a typed view into it, conceptually similar to arrays in C. Because the memory is raw, the JavaScript engine can hand it straight to native code without converting each element into a JavaScript representation. That makes typed arrays substantially faster than ordinary JavaScript arrays whenever binary data has to cross the language boundary into WebGL or similar APIs.
A typed array view presents a slice of an ArrayBuffer as if it were an array of a single numeric type. Views exist for the usual numeric types with straightforward names — Float32Array, Float64Array, Int32Array, Uint8Array — plus Uint8ClampedArray, which has replaced the old pixel array type in Canvas's ImageData. The DataView is the other kind of view, designed for heterogeneous data. Instead of array semantics, DataView offers a get/set API for reading and writing values of any type at any byte offset, which suits file headers and other struct-like layouts.
Constructing buffers and views
The quickest way to start is to create a typed array view of a given size and type:
// Typed array views work pretty much like normal arrays.
var f64a = new Float64Array(8);
f64a[0] = 10;
f64a[1] = 20;
f64a[2] = f64a[0] + f64a[1];
All typed array views share the same API, so once you have used one you basically know them all. The following creates one view of each type currently available:
// Floating point arrays.
var f64 = new Float64Array(8);
var f32 = new Float32Array(16);
// Signed integer arrays.
var i32 = new Int32Array(16);
var i16 = new Int16Array(32);
var i8 = new Int8Array(64);
// Unsigned integer arrays.
var u32 = new Uint32Array(16);
var u16 = new Uint16Array(32);
var u8 = new Uint8Array(64);
var pixels = new Uint8ClampedArray(64);
Uint8ClampedArray is special: it clamps input values into the 0–255 range. This is convenient for Canvas image processing, because you no longer need to clamp results manually to prevent 8-bit overflow. For example, applying a gamma factor to an image held in a Uint8Array needs explicit clamping:
u8[i] = Math.min(255, Math.max(0, u8[i] * gamma));
With Uint8ClampedArray that manual clamping disappears:
pixels[i] *= gamma;
Alternatively, you can create an ArrayBuffer first and then attach views to it. APIs that supply external data typically hand you an ArrayBuffer, so this is how you obtain a typed array view over that data:
var ab = new ArrayBuffer(256); // 256-byte ArrayBuffer.
var faFull = new Uint8Array(ab);
var faFirstHalf = new Uint8Array(ab, 0, 128);
var faThirdQuarter = new Uint8Array(ab, 128, 64);
var faRest = new Uint8Array(ab, 192);
It is also legal to have several views over the same buffer:
var fa = new Float32Array(64);
var ba = new Uint8Array(fa.buffer, 0, Float32Array.BYTES_PER_ELEMENT); // First float of fa.
For copying between typed arrays, the fastest approach is the set method. To mimic memcpy, wrap both buffers in Uint8Array views and copy from one to the other:
function memcpy(dst, dstOffset, src, srcOffset, length) {
var dstU8 = new Uint8Array(dst, dstOffset, length);
var srcU8 = new Uint8Array(src, srcOffset, length);
dstU8.set(srcU8);
};
Working with mixed data: DataView
When an ArrayBuffer holds data of mixed types, a DataView is the simplest way to read it. Suppose a file format starts with an 8-bit unsigned integer, then two 16-bit integers, then a payload of 32-bit floats. Typed array views can handle this, but awkwardly; a DataView reads the header cleanly, and a typed array view then covers the float payload:
var dv = new DataView(buffer);
var vector_length = dv.getUint8(0);
var width = dv.getUint16(1); // 0+uint8 = 1 bytes offset
var height = dv.getUint16(3); // 0+uint8+uint16 = 3 bytes offset
var vectors = new Float32Array(width*height*vector_length);
for (var i=0, off=5; i<vectors.length; i++, off+=4) {
vectors[i] = dv.getFloat32(off);
}
The getters above assume big-endian order. If the data is little-endian, pass the optional littleEndian flag:
...
var width = dv.getUint16(1, true);
var height = dv.getUint16(3, true);
...
vectors[i] = dv.getFloat32(off, true);
...
A crucial detail: typed array views always use the CPU's native byte order, which keeps them fast. When endianness matters — for data from files or the network — use a DataView. The DataView also has matching setters named set followed by the type:
dv.setInt32(0, 25, false); // set big-endian int32 at byte offset 0 to 25
dv.setInt32(4, 25); // set big-endian int32 at byte offset 4 to 25
dv.setFloat32(8, 2.5, true); // set little-endian float32 at byte offset 8 to 2.5
Endianness in practice
Endianness, or byte order, describes how multi-byte numbers are laid out in memory. A big-endian architecture stores the most significant byte first; little-endian stores the least significant byte first. The choice is arbitrary at the hardware level, and some CPUs can be configured for either.
You must care about endianness when data crosses a machine boundary. Files and network streams have an intrinsic byte order that has to be stated explicitly so that any CPU can interpret the bytes correctly, regardless of its own native order. On today's networked devices, binary data from a server or another peer must work on both big- and little-endian hardware.
DataView exists precisely for this. It always operates with a specified endianness — you name big or little on every value access — so reads and writes of file or network data produce consistent results no matter the underlying architecture.
In practice, when binary data arrives from a server, you should scan it once with a DataView, converting values into the structures your application uses internally. Avoid using multi-byte typed array views (Int16Array, Uint16Array, etc.) directly on data fetched via XMLHttpRequest, FileReader, or similar I/O, because those views follow the CPU's native byte order.
Two examples illustrate the pattern. The Windows BMP format stores all integers little-endian. The following snippet parses the start of a BMP header using the accompanying DataStream.js library:
function parseBMP(arrayBuffer) {
var stream = new DataStream(arrayBuffer, 0,
DataStream.LITTLE_ENDIAN);
var header = stream.readUint8Array(2);
var fileSize = stream.readUint32();
// Skip the next two 16-bit integers
stream.readUint16();
stream.readUint16();
var pixelOffset = stream.readUint32();
// Now parse the DIB header
var dibHeaderSize = stream.readUint32();
var imageWidth = stream.readInt32();
var imageHeight = stream.readInt32();
// ...
}
The second example comes from the WebGL HDR rendering demo, which downloads raw little-endian floats for high dynamic range textures. This code interprets those floats correctly on any architecture (the variable arrayBuffer is an ArrayBuffer just received via XMLHttpRequest):
var arrayBuffer = ...;
var data = new DataView(arrayBuffer);
var tempArray = new Float32Array(
data.byteLength / Float32Array.BYTES_PER_ELEMENT);
var len = tempArray.length;
// Incoming data is raw floating point values
// with little-endian byte ordering.
for (var jj = 0; jj < len; ++jj) {
tempArray[jj] =
data.getFloat32(jj * Float32Array.BYTES_PER_ELEMENT, true);
}
gl.texImage2D(...other arguments...,
gl.RGB, gl.FLOAT, tempArray);
The general rule: when data arrives from the web, make one pass with a DataView. Store the resulting values either in JavaScript objects, for modest amounts of structured data, or in typed array views, for larger blocks. Use DataView for outgoing data as well, passing the appropriate littleEndian argument to the setters so you produce the file or wire format you intend. Remember that any data crossing the network has an implicit format and, for multi-byte values, an endianness — document that format explicitly.
Where the browser uses typed arrays
A number of browser APIs now build on typed arrays: WebGL, Canvas, the Web Audio API, XMLHttpRequest, WebSockets, Web Workers, the Media Source API, and the File APIs. The list reflects that typed arrays suit both performance-sensitive multimedia work and efficient data movement.
WebGL
WebGL was the original motivation. Buffer contents are set through gl.bufferData() with a typed array:
var floatArray = new Float32Array([1,2,3,4,5,6,7,8]);
gl.bufferData(gl.ARRAY_BUFFER, floatArray);
Texture data is likewise passed in as typed arrays:
var pixels = new Uint8Array(16*16*4); // 16x16 RGBA image
gl.texImage2D(
gl.TEXTURE_2D, // target
0, // mip level
gl.RGBA, // internal format
16, 16, // width and height
0, // border
gl.RGBA, //format
gl.UNSIGNED_BYTE, // type
pixels // texture data
);
Reading pixels back from the WebGL context also requires a typed array:
var pixels = new Uint8Array(320*240*4); // 320x240 RGBA image
gl.readPixels(0, 0, 320, 240, gl.RGBA, gl.UNSIGNED_BYTE, pixels);
Canvas 2D
Canvas ImageData now conforms to the typed array spec. You can obtain pixel data as a typed array without interacting with the canvas element directly, and you can create or edit pixel buffers in the same way:
var imageData = ctx.getImageData(0,0, 200, 100);
var typedArray = imageData.data // data is a Uint8ClampedArray
XMLHttpRequest2
XMLHttpRequest can return binary data as an ArrayBuffer, avoiding the detour of parsing a string into an array. This is handy for piping fetched data into multimedia APIs or parsing downloaded binary files. Just set responseType to 'arraybuffer':
xhr.responseType = 'arraybuffer';
As always, keep endianness in mind for anything fetched from a server.
File APIs
FileReader can load file content into an ArrayBuffer. From there, attach typed array views or a DataView to inspect and modify the contents:
reader.readAsArrayBuffer(file);
Endianness applies here too.
Transferable objects
Passing binary data between windows and Web Workers gets much faster with transferable objects. When you transfer an object to a Worker, the sending thread loses access to it and the Worker gains ownership. The implementation can then avoid copying entirely, transferring only the ownership of the buffer.
Workers use webkitPostMessage rather than postMessage. It behaves similarly but takes a second argument: an array of objects you wish to transfer:
worker.webkitPostMessage(oneGBTypedArray, [oneGBTypedArray]);
The Worker can pass objects back to the main thread the same way:
webkitPostMessage({results: grand, youCanHaveThisBack: oneGBTypedArray}, [oneGBTypedArray]);
Zero copies involved.
Media Source API
Media elements now accept typed array data through the Media Source API. You can push video data directly into a video element with webkitSourceAppend, which appends the footage to whatever the element already holds. This works well for interstitials, playlists, streaming, or any case where one video element needs to play several clips in sequence:
video.webkitSourceAppend(uint8Array);
Binary WebSockets
WebSockets also accept typed arrays, so you can skip stringifying data. That helps build efficient protocols and reduce traffic:
socket.binaryType = 'arraybuffer';
Libraries that build on typed arrays
While DataView handles the low-level mechanics of reading binary data, several libraries layer more convenient abstractions on top of typed arrays.
jDataView
jDataView is a shim that implements the DataView API across browsers. It was originally written when DataView was only available in WebKit, and while Firefox and most other engines have since added native support, the library remains useful for legacy environments. Eric Bidelman of the Chrome Developer Relations team demonstrated it in a small example that reads MP3 ID3 tags:
var dv = new jDataView(arraybuffer);
// "TAG" starts at byte -128 from EOF.
// See http://en.wikipedia.org/wiki/ID3
if (dv.getString(3, dv.byteLength - 128) == 'TAG') {
var title = dv.getString(30, dv.tell());
var artist = dv.getString(30, dv.tell());
var album = dv.getString(30, dv.tell());
var year = dv.getString(4, dv.tell());
} else {
// no ID3v1 data found.
}
stringencoding
The stringencoding library implements the Typed Array string encoding spec that has been proposed for broader adoption. It provides a preview of the standard API for converting between strings and typed arrays:
var uint8array = new TextEncoder(encoding).encode(string);
var string = new TextDecoder(encoding).decode(uint8array);
BitView.js
BitView.js applies the DataView idea one level further down: it works with individual bits rather than bytes. You can read and set the value of any bit at a given bit offset within an ArrayBuffer, and the library also includes methods for storing 6-bit and 12-bit integers at arbitrary bit offsets.
The 12-bit integer support is useful for compressing screen coordinates, since displays rarely exceed 4096 pixels along their longest axis. Replacing 32-bit integers with 12-bit ones cuts the data size by 62%. In a more ambitious case, a Shapefile viewer replaced the 64-bit floating-point coordinates in the source data with 12-bit base coordinates plus 6-bit deltas for successive points, shrinking the payload to a tenth of its original size. You can see the demo here:
var bv = new BitView(arrayBuffer);
bv.setBit(4, 1); // Set fourth bit of arrayBuffer to 1.
bv.getBit(17); // Get 17th bit of arrayBuffer.
bv.getBit(50*8 + 3); // Get third bit of 50th byte in arrayBuffer.
bv.setInt6(3, 18); // Write 18 as a 6-bit int to bit position 3 in arrayBuffer.
bv.getInt12(9); // Read a 12-bit int from bit position 9 in arrayBuffer.
DataStream.js
Reading an ArrayBuffer that came from an XHR is straightforward for individual values, but parsing structs and arrays out of a buffer by hand gets tedious quickly. DataStream.js is a library that reads and writes scalars, strings, arrays and structs from ArrayBuffers in a file-like, endian-safe fashion.
Basic scalar reads are as simple as:
// without DataStream.js
var dv = new DataView(buffer);
var f32 = new Float32Array(buffer.byteLength / 4);
var littleEndian = true;
for (var i = 0; i<f32.length; i++) {
f32[i] = dv.getFloat32(i*4, littleEndian);
}
// with DataStream.js
var ds = new DataStream(buffer);
ds.endianness = DataStream.LITTLE_ENDIAN;
var f32 = ds.readFloat32Array(ds.byteLength / 4);
The library's real strength shows when reading structured data. A typical use case is parsing the marker structure of a JPEG file:
// without DataStream.js
var dv = new DataView(buffer);
var objs = [];
for (var i=0; i<buffer.byteLength;) {
var obj = {};
obj.tag = dv.getUint16(i);
i += 2;
obj.length = dv.getUint16(i);
i += 2;
obj.data = new Uint8Array(obj.length - 2);
for (var j=0; j<obj.data.length; j++,i++) {
obj.data[j] = dv.getUint8(i);
}
objs.push(obj);
}
// with DataStream.js
var ds = new DataStream(buffer);
ds.endianness = ds.BIG_ENDIAN;
var objs = [];
while (!ds.isEof()) {
var obj = {};
obj.tag = ds.readUint16();
obj.length = ds.readUint16();
obj.data = ds.readUint8Array(obj.length - 2);
objs.push(obj);
}
DataStream.js also provides a readStruct method that takes a flat definition array of [name, type] pairs. The type element can be a scalar type, a nested struct (as an array), or a three-element array describing a field of arrays, where the first element is unused, the second is the element type, and the third is the length—either an integer or a reference to a previously read field:
// with DataStream.readStruct
ds.readStruct([
'objs', ['[]', [ // objs: array of tag,length,data structs
'tag', 'uint16',
'length', 'uint16',
'data', ['[]', 'uint8', function(s,ds){ return s.length - 2; }], // get length with a function
'*'] // read in as many struct as there are
]);
The set of supported types is defined as:
Number types
Unsuffixed number types use DataStream endianness.
To explicitly specify endianness, suffix the type with
'le' for little-endian or 'be' for big-endian,
e.g. 'int32be' for big-endian int32.
'uint8' -- 8-bit unsigned int
'uint16' -- 16-bit unsigned int
'uint32' -- 32-bit unsigned int
'int8' -- 8-bit int
'int16' -- 16-bit int
'int32' -- 32-bit int
'float32' -- 32-bit float
'float64' -- 64-bit float
String types
'cstring' -- ASCII string terminated by a zero byte.
'string:N' -- ASCII string of length N.
'string,CHARSET:N' -- String of byteLength N encoded with given CHARSET.
'u16string:N' -- UCS-2 string of length N in DataStream endianness.
'u16stringle:N' -- UCS-2 string of length N in little-endian.
'u16stringbe:N' -- UCS-2 string of length N in big-endian.
Complex types
[name, type, name_2, type_2, ..., name_N, type_N] -- Struct
function(dataStream, struct) {} -- Callback function to read and return data.
{get: function(dataStream, struct) {}, set: function(dataStream, struct) {}}
-- Getter/setter functions to reading and writing data. Handy for using the
same struct definition for both reading and writing.
['', type, length] -- Array of given type and length. The length can be either
a number, a string that references a previously-read
field, or a callback function(struct, dataStream, type){}.
If length is set to '*', elements are read from the
DataStream until a read fails.
You can watch readStruct in action on live JPEG metadata here; the demo pairs DataStream.js with jpg.js for decoding and rendering the image.
Origins and design rationale
Typed arrays trace back to the early development of WebGL. Passing standard JavaScript arrays to the graphics driver forced the binding to walk the array, cast each element to a native type, and allocate a separate native buffer before any data could reach the GPU. That conversion was a performance bottleneck.
Mozilla's Vladimir Vukicevic wrote CanvasFloatArray—a C-style float array exposed to JavaScript—to allow the browser to hand the buffer straight to WebGL with no conversion step. The class was renamed to WebGLFloatArray, then settled at Float32Array, split into a backing ArrayBuffer plus a typed view. The same pattern was extended to signed and unsigned integers of various widths.
The design deliberately pairs two complementary interfaces:
- Typed array views operate on data aligned to the host CPU's native endianness. Because engines can optimize them heavily, they are the best choice for assembling buffers for graphics and other performance-critical work.
- DataView is built for file and network I/O, where data carries an explicit endianness and may not be aligned for maximum speed.



