JS: ArrayBuffer

By Xah Lee. Date: .
xtodo

ai answer

JavaScript ArrayBuffer Tutorial

What is an ArrayBuffer?

ArrayBuffer is a built-in JavaScript object that represents a fixed-length raw binary data buffer. It is the foundation for handling binary data in modern JavaScript (especially in browsers and Node.js).

Think of it as a contiguous block of memory (like a byte array) that you can read from and write to using views — most commonly TypedArrays (Uint8Array, Float32Array, etc.) or DataView.

Key characteristics:

Creating an ArrayBuffer

// Create a buffer of 16 bytes (empty, initialized to 0)
const buffer = new ArrayBuffer(16);

console.log(buffer.byteLength); // 16

You can also create one from existing data using TypedArray or other methods (covered later).

Basic Properties

Property Description
byteLength Size of the buffer in bytes (read-only)
byteOffset (On views) offset from start of buffer
detached true if the buffer was transferred/detached (ES2021+)
const buf = new ArrayBuffer(32);
console.log(buf.byteLength); // 32

Views: Typed Arrays and DataView

You cannot directly read/write an ArrayBuffer. You must create a view.

1. Typed Arrays (most common)

const buffer = new ArrayBuffer(16);

// Create different typed views on the same buffer
const uint8 = new Uint8Array(buffer);        // unsigned 8-bit integers (bytes)
const int16 = new Int16Array(buffer);        // signed 16-bit integers
const float32 = new Float32Array(buffer);    // 32-bit floats

uint8[0] = 255;     // Set first byte
console.log(uint8[0]);     // 255
console.log(int16[0]);     // Depends on endianness (usually -1 for signed)

Common Typed Arrays:

2. DataView (for mixed types & explicit endianness)

Use DataView when you need fine-grained control:

const buffer = new ArrayBuffer(16);
const view = new DataView(buffer);

view.setUint8(0, 255);
view.setInt16(1, 300, false); // big-endian (network byte order)
view.setFloat32(4, 3.14, true); // little-endian

console.log(view.getUint8(0));     // 255
console.log(view.getInt16(1, false));

Important Methods & Operations

Slicing

const buffer = new ArrayBuffer(32);
const slice = buffer.slice(8, 24); // Creates a **copy** of bytes 8-23

Transfer (zero-copy move) — ES2018+

const buffer1 = new ArrayBuffer(1024);
const buffer2 = buffer1.transfer(); // buffer1 becomes detached

// Or transfer with limit:
const buffer3 = buffer1.transfer(512);

Useful for passing large buffers between workers without copying.

Checking if detached

console.log(buffer1.detached); // true

Converting Between Formats

// String <-> ArrayBuffer
function stringToArrayBuffer(str) {
  return new TextEncoder().encode(str).buffer;
}

function arrayBufferToString(buf) {
  return new TextDecoder().decode(new Uint8Array(buf));
}

// Blob / File
const blob = new Blob([new Uint8Array(buffer)]);
const file = new File([buffer], "data.bin");

// Fetch
const response = await fetch(url);
const arrayBuffer = await response.arrayBuffer();

Practical Examples

1. Reading a File as Binary

input.addEventListener('change', async (e) => {
  const file = e.target.files[0];
  const buffer = await file.arrayBuffer();

  const uint8 = new Uint8Array(buffer);
  console.log(uint8); // raw bytes
});

2. Custom Binary Protocol (e.g., game networking)

function createMessage(id, payload) {
  const buffer = new ArrayBuffer(4 + payload.length);
  const view = new DataView(buffer);

  view.setUint32(0, id, false);           // 4-byte ID
  new Uint8Array(buffer, 4).set(payload); // payload

  return buffer;
}

3. Copying data between buffers

const src = new Uint8Array([1, 2, 3, 4]);
const dest = new Uint8Array(10);

dest.set(src, 3); // copy src starting at index 3 in dest

Common Use Cases

Best Practices & Gotchas

  1. Always use views — never assume you can treat ArrayBuffer like a normal array.
  2. Endianness matters — network protocols are usually big-endian (false in DataView).
  3. SharedArrayBuffer — for sharing memory across workers (requires special headers for security).
  4. Memory management — Large buffers can cause memory pressure. Use slice() or transfer() wisely.
  5. TypedArray vs DataView:
    • Use TypedArray for speed and simplicity (same type).
    • Use DataView for heterogeneous data or explicit control.
  6. Immutability — Buffers are mutable. Clone with .slice() if needed.
  7. Node.js vs Browser — Most APIs are identical, but Node has Buffer class (which can be created from ArrayBuffer).

Quick Reference Cheat Sheet

new ArrayBuffer(length)
buffer.byteLength
buffer.slice(begin, end)
new Uint8Array(buffer)
new DataView(buffer)
new TextEncoder().encode(str).buffer
new TextDecoder().decode(uint8array)

JS: ArrayBuffer DataView TypedArray