JavaScript TypedArray and Binary Data Complete Guide

Master ArrayBuffer, TypedArray, DataView, and binary data processing techniques

JavaScript TypedArray and Binary Data Complete Guide

TypedArray is a core tool for handling binary data in JavaScript. This article covers the usage and practical applications of these concepts.

ArrayBuffer Basics

Creating Buffers

// Create buffer with specified byte length
const buffer = new ArrayBuffer(16);  // 16 bytes

// Check byte length
buffer.byteLength;  // 16

// ArrayBuffer cannot be read/written directly
// Need views to operate on it

// Copy buffer
const copy = buffer.slice(0, 8);  // Copy first 8 bytes

// Check if ArrayBuffer
buffer instanceof ArrayBuffer;  // true
ArrayBuffer.isView(buffer);     // false

View Concept

// TypedArray and DataView are two types of views
const buffer = new ArrayBuffer(16);

// TypedArray view
const int32View = new Int32Array(buffer);

// DataView view
const dataView = new DataView(buffer);

// Same buffer can have multiple views
const uint8View = new Uint8Array(buffer);
const float32View = new Float32Array(buffer);

// They share the same memory
int32View[0] = 1;
uint8View[0];  // 1 (same memory location)

TypedArray Types

Integer Types

// 8-bit integers
const int8 = new Int8Array(4);    // -128 to 127
const uint8 = new Uint8Array(4);  // 0 to 255

// 8-bit unsigned (clamped)
const uint8c = new Uint8ClampedArray(4);  // 0-255, auto-clamped

// 16-bit integers
const int16 = new Int16Array(4);   // -32768 to 32767
const uint16 = new Uint16Array(4); // 0 to 65535

// 32-bit integers
const int32 = new Int32Array(4);   // -2^31 to 2^31-1
const uint32 = new Uint32Array(4); // 0 to 2^32-1

// 64-bit integers (BigInt)
const bigInt64 = new BigInt64Array(4);
const bigUint64 = new BigUint64Array(4);

// Example: overflow behavior
const u8 = new Uint8Array(1);
u8[0] = 256;  // Becomes 0 (overflow)
u8[0] = -1;   // Becomes 255 (overflow)

const u8c = new Uint8ClampedArray(1);
u8c[0] = 256; // Becomes 255 (clamped to max)
u8c[0] = -1;  // Becomes 0 (clamped to min)

Float Types

// 32-bit float
const float32 = new Float32Array(4);

// 64-bit float
const float64 = new Float64Array(4);

// Float examples
const f32 = new Float32Array(1);
f32[0] = 3.14159265359;
f32[0];  // 3.1415927410125732 (precision loss)

const f64 = new Float64Array(1);
f64[0] = 3.14159265359;
f64[0];  // 3.14159265359 (higher precision)

Creation Methods

// 1. Specify length
const arr1 = new Int32Array(4);

// 2. From array
const arr2 = new Int32Array([1, 2, 3, 4]);

// 3. From ArrayBuffer
const buffer = new ArrayBuffer(16);
const arr3 = new Int32Array(buffer);

// 4. With offset and length
const arr4 = new Int32Array(buffer, 4, 2);  // From byte 4, 2 elements

// 5. From another TypedArray
const arr5 = new Float32Array(arr2);  // Type conversion

// 6. Using from method
const arr6 = Int32Array.from([1, 2, 3]);
const arr7 = Int32Array.of(1, 2, 3);

TypedArray Operations

Basic Operations

const arr = new Int32Array([10, 20, 30, 40]);

// Read
arr[0];       // 10
arr.length;   // 4

// Write
arr[0] = 100;

// Iterate
for (const value of arr) {
  console.log(value);
}

arr.forEach((value, index) => {
  console.log(index, value);
});

// Array methods
arr.map(x => x * 2);      // Int32Array [200, 40, 60, 80]
arr.filter(x => x > 15);  // Int32Array [20, 30, 40]
arr.reduce((a, b) => a + b, 0);  // 190
arr.find(x => x > 25);    // 30
arr.indexOf(20);          // 1
arr.includes(30);         // true

Copy and Fill

const arr = new Int32Array(8);

// Fill
arr.fill(42);           // Fill all with 42
arr.fill(0, 2, 5);      // Fill indices 2-4 with 0

// Set
const source = new Int32Array([1, 2, 3]);
const target = new Int32Array(5);
target.set(source);      // [1, 2, 3, 0, 0]
target.set(source, 2);   // [1, 2, 1, 2, 3]

// Copy within
arr.copyWithin(0, 3, 5);  // Copy indices 3-4 to index 0

// Slice
const slice = arr.slice(1, 4);  // New TypedArray

// Subarray (shares memory)
const sub = arr.subarray(1, 4);  // View, shares same buffer

Sort and Reverse

const arr = new Float32Array([3.1, 1.2, 4.5, 2.3]);

// Sort (in-place)
arr.sort();  // [1.2, 2.3, 3.1, 4.5]

// Custom sort
arr.sort((a, b) => b - a);  // Descending

// Reverse
arr.reverse();

DataView

Creation and Usage

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

// Write data
view.setInt8(0, 127);
view.setInt16(1, 32767);
view.setInt32(3, 2147483647);
view.setFloat32(7, 3.14);
view.setFloat64(11, Math.PI);

// Read data
view.getInt8(0);      // 127
view.getInt16(1);     // 32767
view.getInt32(3);     // 2147483647
view.getFloat32(7);   // 3.14
view.getFloat64(11);  // 3.141592653589793

Endianness

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

// Write 0x12345678
// Big-Endian - default
view.setUint32(0, 0x12345678, false);
// Memory: 12 34 56 78

// Little-Endian
view.setUint32(0, 0x12345678, true);
// Memory: 78 56 34 12

// Reading also needs endianness specified
view.getUint32(0, false);  // Big-endian read
view.getUint32(0, true);   // Little-endian read

// Detect system endianness
function isLittleEndian() {
  const buffer = new ArrayBuffer(2);
  new DataView(buffer).setInt16(0, 256, true);
  return new Int16Array(buffer)[0] === 256;
}

Practical Applications

Image Data Processing

// Canvas image data
const canvas = document.createElement('canvas');
const ctx = canvas.getContext('2d');
const imageData = ctx.getImageData(0, 0, 100, 100);

// imageData.data is Uint8ClampedArray
const pixels = imageData.data;

// Iterate each pixel (RGBA)
for (let i = 0; i < pixels.length; i += 4) {
  const r = pixels[i];     // Red
  const g = pixels[i + 1]; // Green
  const b = pixels[i + 2]; // Blue
  const a = pixels[i + 3]; // Alpha

  // Grayscale
  const gray = (r + g + b) / 3;
  pixels[i] = gray;
  pixels[i + 1] = gray;
  pixels[i + 2] = gray;
}

ctx.putImageData(imageData, 0, 0);

// Invert colors
for (let i = 0; i < pixels.length; i += 4) {
  pixels[i] = 255 - pixels[i];
  pixels[i + 1] = 255 - pixels[i + 1];
  pixels[i + 2] = 255 - pixels[i + 2];
}

Audio Data Processing

// Web Audio API
const audioCtx = new AudioContext();

// Create audio buffer
const sampleRate = audioCtx.sampleRate;
const duration = 2;  // 2 seconds
const buffer = audioCtx.createBuffer(
  2,                    // Channels
  sampleRate * duration, // Total samples
  sampleRate            // Sample rate
);

// Get channel data (Float32Array)
const channelData = buffer.getChannelData(0);

// Generate sine wave
const frequency = 440;  // A4 note
for (let i = 0; i < channelData.length; i++) {
  channelData[i] = Math.sin(2 * Math.PI * frequency * i / sampleRate);
}

// Play
const source = audioCtx.createBufferSource();
source.buffer = buffer;
source.connect(audioCtx.destination);
source.start();

File Operations

// Read file as ArrayBuffer
async function readFileAsBuffer(file) {
  return new Promise((resolve, reject) => {
    const reader = new FileReader();
    reader.onload = () => resolve(reader.result);
    reader.onerror = reject;
    reader.readAsArrayBuffer(file);
  });
}

// Parse PNG header
async function parsePNGHeader(file) {
  const buffer = await readFileAsBuffer(file);
  const view = new DataView(buffer);

  // PNG signature: 89 50 4E 47 0D 0A 1A 0A
  const signature = new Uint8Array(buffer, 0, 8);
  const expected = [0x89, 0x50, 0x4E, 0x47, 0x0D, 0x0A, 0x1A, 0x0A];

  const isPNG = signature.every((byte, i) => byte === expected[i]);

  if (!isPNG) {
    throw new Error('Not a PNG file');
  }

  // IHDR chunk
  const width = view.getUint32(16);
  const height = view.getUint32(20);

  return { width, height };
}

Network Protocols

// Create simple binary protocol message
function createMessage(type, payload) {
  const payloadBytes = new TextEncoder().encode(payload);
  const buffer = new ArrayBuffer(4 + payloadBytes.length);
  const view = new DataView(buffer);

  // Header: type (2 bytes) + length (2 bytes)
  view.setUint16(0, type);
  view.setUint16(2, payloadBytes.length);

  // Body
  new Uint8Array(buffer, 4).set(payloadBytes);

  return buffer;
}

// Parse message
function parseMessage(buffer) {
  const view = new DataView(buffer);

  const type = view.getUint16(0);
  const length = view.getUint16(2);
  const payload = new TextDecoder().decode(
    new Uint8Array(buffer, 4, length)
  );

  return { type, payload };
}

// WebSocket binary data
const ws = new WebSocket('wss://example.com');
ws.binaryType = 'arraybuffer';

ws.onmessage = (event) => {
  const buffer = event.data;
  const message = parseMessage(buffer);
  console.log(message);
};

ws.send(createMessage(1, 'Hello'));

Base64 Encoding

// ArrayBuffer to Base64
function bufferToBase64(buffer) {
  const bytes = new Uint8Array(buffer);
  let binary = '';
  for (let i = 0; i < bytes.length; i++) {
    binary += String.fromCharCode(bytes[i]);
  }
  return btoa(binary);
}

// Base64 to ArrayBuffer
function base64ToBuffer(base64) {
  const binary = atob(base64);
  const bytes = new Uint8Array(binary.length);
  for (let i = 0; i < binary.length; i++) {
    bytes[i] = binary.charCodeAt(i);
  }
  return bytes.buffer;
}

// Usage
const buffer = new Uint8Array([72, 101, 108, 108, 111]).buffer;
const base64 = bufferToBase64(buffer);  // 'SGVsbG8='
const decoded = base64ToBuffer(base64);

Hexadecimal Conversion

// ArrayBuffer to hex string
function bufferToHex(buffer) {
  const bytes = new Uint8Array(buffer);
  return Array.from(bytes)
    .map(b => b.toString(16).padStart(2, '0'))
    .join('');
}

// Hex string to ArrayBuffer
function hexToBuffer(hex) {
  const bytes = new Uint8Array(hex.length / 2);
  for (let i = 0; i < bytes.length; i++) {
    bytes[i] = parseInt(hex.substr(i * 2, 2), 16);
  }
  return bytes.buffer;
}

// Usage
const buffer = new Uint8Array([255, 128, 0]).buffer;
const hex = bufferToHex(buffer);  // 'ff8000'
const decoded = hexToBuffer(hex);

SharedArrayBuffer

Basic Usage

// Create shared buffer
const sharedBuffer = new SharedArrayBuffer(16);

// Can be shared between Workers
// Main thread
const worker = new Worker('worker.js');
worker.postMessage({ buffer: sharedBuffer });

// worker.js
self.onmessage = (e) => {
  const buffer = e.data.buffer;
  const view = new Int32Array(buffer);
  view[0] = 42;  // Changes reflect in main thread
};

Atomics Operations

const buffer = new SharedArrayBuffer(16);
const view = new Int32Array(buffer);

// Atomic operations
Atomics.store(view, 0, 42);      // Atomic write
Atomics.load(view, 0);           // Atomic read

// Atomic add/subtract
Atomics.add(view, 0, 5);         // Atomic add, returns old value
Atomics.sub(view, 0, 3);         // Atomic subtract

// Atomic bitwise
Atomics.and(view, 0, 0b1111);    // Atomic AND
Atomics.or(view, 0, 0b1111);     // Atomic OR
Atomics.xor(view, 0, 0b1111);    // Atomic XOR

// Atomic exchange
Atomics.exchange(view, 0, 100);  // Set new, return old
Atomics.compareExchange(view, 0, 100, 200);  // CAS operation

// Wait and notify
Atomics.wait(view, 0, 0);        // Wait for value change
Atomics.notify(view, 0, 1);      // Notify waiters

Best Practices Summary

TypedArray Best Practices:
┌─────────────────────────────────────────────────────┐
│                                                     │
│   Type Selection                                    │
│   ├── Image processing: Uint8ClampedArray          │
│   ├── Audio processing: Float32Array               │
│   ├── Integer math: Int32Array / Uint32Array       │
│   └── High precision float: Float64Array           │
│                                                     │
│   Performance                                       │
│   ├── Pre-allocate appropriately sized buffers     │
│   ├── Use subarray to avoid copying                │
│   ├── Batch operations over element-by-element     │
│   └── Mind byte alignment                          │
│                                                     │
│   Use Cases                                         │
│   ├── Graphics and game development                │
│   ├── Audio/video processing                       │
│   ├── File parsing                                 │
│   └── Network protocols                            │
│                                                     │
└─────────────────────────────────────────────────────┘
TypeBytesValue Range
Int8Array1-128 to 127
Uint8Array10 to 255
Int16Array2-32768 to 32767
Uint16Array20 to 65535
Int32Array4-2^31 to 2^31-1
Float32Array4IEEE 754 single
Float64Array8IEEE 754 double

Master TypedArray and binary data processing to unlock JavaScript’s low-level capabilities.