JavaScript Typed Arrays Table

PieceWhat it doesField note
new Uint8Array(8)Fixed-type array viewOne type per array, no boxing - the fast dense numbers
ArrayBufferThe raw byte slabNo direct access - views read it; fixed length forever
DataViewByte-precise read/writegetUint32(offset, littleEndian) - file format parsing's tool
Typed array OF a bufferView, not copyconst v = new Uint8Array(buffer) shares the bytes - writes show through
subarray()Window into the dataShares memory - slice() copies, subarray() does not
TextEncoder / TextDecoderStrings to bytes, backUTF-8 default - the WebSocket/file boundary pair
Blob / FileBinary with a MIMEFeeds img src, download links, FormData - the user-facing wrapper
transfer to workerZero-copy moveArrayBuffers transfer (sender loses) - big payload's only fast path
Reference: the MDN typed arrays reference. Binary JavaScript has three layers: the ArrayBuffer is the raw byte slab (no direct reads), typed arrays are TYPED VIEWS over it (Uint8Array, Float32Array - dense, no boxing, math-fast), and DataView is the byte-precise reader for formats with mixed-width fields and endianness. Views SHARE their buffer - subarray() is a window, slice() is a copy, and writes through any view show in all of them. Bottom line: TextEncoder/TextDecoder bridge strings at the boundary, Blob wraps bytes for the DOM (img src, downloads, FormData), and transferring an ArrayBuffer to a worker moves it in O(1) - the only fast path for big payloads. Related tools: workers table (transferables across threads), canvas table (ImageData is a typed array), and JSON table (the text format typed arrays replace).

Binary JavaScript has three layers: the ArrayBuffer is the raw byte slab (no direct reads, fixed length), typed arrays are TYPED VIEWS over it (Uint8Array, Float32Array - dense, unboxed, math-fast), and DataView is the byte-precise reader for formats with mixed-width fields and endianness.

Bottom line: views SHARE their buffer. Two typed arrays over one ArrayBuffer see each other's writes; subarray() is a window (no copy), slice() is a copy. Every memory bug in binary JS traces to forgetting which side of that line an API sits on.

The honest part: regular Arrays are object arrays - flexible but boxed and sparse. Typed arrays trade that flexibility for contiguous memory and real numeric types, which is what ImageData, WebGL buffers, WebAudio samples, WebSocket frames and file parsing are made of. If you are not touching binary data, you do not need them.

How to use

  1. Bridge strings at the boundary: new TextEncoder().encode(str) gives UTF-8 bytes; new TextDecoder().decode(bytes) returns - the WebSocket and file-format pair.
  2. Parse a binary format with DataView: dv.getUint32(0, false) reads a big-endian 32-bit int at offset 0 - explicit endianness per field beats guessing.
  3. Ship bytes to a worker fast: worker.postMessage(buffer, [buffer]) - the TRANSFER list moves ownership in O(1); treat the sender's reference as dead.

Frequently asked questions

What is the relationship between an ArrayBuffer and a typed array?

Memory versus window. The ArrayBuffer is a fixed-length block of raw bytes with NO read/write API of its own; a typed array (or DataView) is a VIEW that interprets those bytes as typed values. new Uint8Array(buffer) does not copy - it looks at the same bytes through an unsigned-8-bit lens, so writing v[0] = 255 is visible to every other view on that buffer, and to a DataView reading it as a 32-bit int. This is also how you reinterpret data cheaply: one buffer, a Float32Array view for math and a Uint8Array view for storage - the views are the API, the buffer is the memory.

When is subarray() right versus slice()?

subarray() creates a new VIEW over the same bytes: zero-copy, instant, but any write through the subarray lands in the original - and keeping the subarray alive keeps the WHOLE original buffer alive (a small window pins a large slab from garbage collection). slice() copies the bytes out: independent, safe to hold long-term, costs memory and time proportional to length. The rule: temporary parsing windows take subarray(); data that escapes your function, gets stored, or must not alias takes slice(). The bug shape is always the same - a cached subarray silently mutating (or pinning) the source.

Why does TextEncoder exist when strings feel like data already?

Because a JavaScript string is UTF-16 code units in memory, while every boundary - files, WebSockets, fetch bodies, crypto - speaks BYTES with an encoding. TextEncoder.encode('héllo') produces UTF-8 bytes (6 bytes here, not 5); TextDecoder decodes the reverse, and it handles invalid byte sequences with explicit policies rather than silent mojibake. Note the asymmetry worth knowing: encode() is stateless and reusable, while decode() is stateful for STREAMING - multi-byte characters split across chunks need decode(value, { stream: true }) on each piece.

What makes transferring an ArrayBuffer to a worker O(1)?

Ownership moves instead of data. Normally postMessage structured-clones the buffer - a byte-for-byte copy proportional to size (100MB payload = 100MB copy). The transfer list (the second postMessage argument) instead reassigns the underlying memory to the worker's realm: zero copying, microseconds, but the SENDER's reference is neutered (byteLength becomes 0, further reads fail) - a move, not a share. The pipeline pattern: read a file into a buffer on the main thread, transfer to the worker for heavy parsing, transfer the result buffer back. For WebAssembly-heavy apps this zero-copy discipline is the difference between snappy and dead.

Related tools