Typed Arrays and ArrayBuffer - Binary Data Handling
Typed Arrays provide a mechanism for accessing raw binary data in JavaScript. They are essential for working with files, network protocols, WebGL graphics, audio processing, and other performance-critical operations that require direct memory manipulation.
What are Typed Arrays?
Typed Arrays are array-like views that provide a mechanism for accessing raw binary data. Unlike regular arrays, Typed Arrays:
- Store data in a fixed-size binary buffer
- Can only contain numbers of a specific type
- Provide better performance for numeric operations
- Are used in WebGL, Canvas, Web Audio, and File APIs
- Cannot be resized after creation
Key Fact: Typed Arrays were initially created for WebGL to efficiently handle 3D graphics data, but they're now used in many Web APIs.
ArrayBuffer - The Foundation
ArrayBuffer is a generic, fixed-length raw binary data buffer:
// Create a 16-byte buffer
const buffer = new ArrayBuffer(16);
console.log(buffer.byteLength); // 16
console.log(buffer); // ArrayBuffer { byteLength: 16 }
// ArrayBuffer itself cannot be directly manipulated
// You need a "view" to read/write data
// Check if ArrayBuffer is supported
console.log(typeof ArrayBuffer); // 'function'
// You cannot resize an ArrayBuffer
// To "resize", you must create a new one and copy data
Important: ArrayBuffer is just raw memory. You cannot read or write to it directly. You need a Typed Array view or DataView to access the data.
Typed Array Types
JavaScript provides several Typed Array constructors for different numeric types:
Integer Types:
Int8Array - 8-bit signed integer (-128 to 127)
Uint8Array - 8-bit unsigned integer (0 to 255)
Uint8ClampedArray- 8-bit unsigned integer, clamped (0 to 255)
Int16Array - 16-bit signed integer (-32768 to 32767)
Uint16Array - 16-bit unsigned integer (0 to 65535)
Int32Array - 32-bit signed integer (-2^31 to 2^31-1)
Uint32Array - 32-bit unsigned integer (0 to 2^32-1)
Floating Point Types:
Float32Array - 32-bit IEEE floating point
Float64Array - 64-bit IEEE floating point
BigInt Types (ES2020+):
BigInt64Array - 64-bit signed integer
BigUint64Array - 64-bit unsigned integer
Creating Typed Arrays
You can create Typed Arrays in several ways:
// Method 1: Create with length
const int8 = new Int8Array(4);
console.log(int8); // Int8Array [0, 0, 0, 0]
console.log(int8.length); // 4
console.log(int8.byteLength); // 4 bytes
// Method 2: Create from array
const uint8 = new Uint8Array([10, 20, 30, 40]);
console.log(uint8); // Uint8Array [10, 20, 30, 40]
// Method 3: Create from ArrayBuffer
const buffer = new ArrayBuffer(8);
const int16 = new Int16Array(buffer);
console.log(int16.length); // 4 (8 bytes / 2 bytes per element)
// Method 4: Create from another Typed Array
const float32 = new Float32Array(uint8);
console.log(float32); // Float32Array [10, 20, 30, 40]
// Method 5: Using .of() static method
const arr = Uint8Array.of(1, 2, 3, 4);
console.log(arr); // Uint8Array [1, 2, 3, 4]
// Method 6: Using .from() static method
const arr2 = Uint8Array.from([5, 6, 7, 8]);
console.log(arr2); // Uint8Array [5, 6, 7, 8]
Working with Typed Arrays
Typed Arrays behave similarly to regular arrays but with some differences:
const numbers = new Uint8Array([1, 2, 3, 4, 5]);
// Accessing elements
console.log(numbers[0]); // 1
console.log(numbers[2]); // 3
// Setting elements
numbers[0] = 10;
numbers[1] = 20;
console.log(numbers); // Uint8Array [10, 20, 3, 4, 5]
// Array methods work
console.log(numbers.length); // 5
console.log(numbers.map(x => x * 2)); // Uint8Array [20, 40, 6, 8, 10]
console.log(numbers.filter(x => x > 3)); // Uint8Array [10, 20, 4, 5]
console.log(numbers.reduce((sum, x) => sum + x, 0)); // 42
// Slicing creates a new Typed Array
const slice = numbers.slice(1, 3);
console.log(slice); // Uint8Array [20, 3]
// Iteration
for (const num of numbers) {
console.log(num);
}
// Spread operator works
console.log([...numbers]); // [10, 20, 3, 4, 5]
Value Clamping and Overflow
Different Typed Arrays handle out-of-range values differently:
// Uint8Array wraps around (0-255)
const uint8 = new Uint8Array(3);
uint8[0] = 255;
uint8[1] = 256; // Wraps to 0
uint8[2] = -1; // Wraps to 255
console.log(uint8); // Uint8Array [255, 0, 255]
// Uint8ClampedArray clamps values (0-255)
const clamped = new Uint8ClampedArray(3);
clamped[0] = 255;
clamped[1] = 256; // Clamped to 255
clamped[2] = -1; // Clamped to 0
console.log(clamped); // Uint8ClampedArray [255, 255, 0]
// Int8Array wraps with sign (-128 to 127)
const int8 = new Int8Array(3);
int8[0] = 127;
int8[1] = 128; // Wraps to -128
int8[2] = -129; // Wraps to 127
console.log(int8); // Int8Array [127, -128, 127]
// Float arrays don't wrap
const float = new Float32Array(2);
float[0] = 1.5;
float[1] = 3.14159;
console.log(float); // Float32Array [1.5, 3.14159]
Use Case: Uint8ClampedArray is specifically designed for Canvas pixel manipulation where values must be clamped to 0-255.
DataView - Flexible Binary Access
DataView provides a low-level interface for reading and writing multiple number types in an ArrayBuffer:
const buffer = new ArrayBuffer(8);
const view = new DataView(buffer);
// Write different types to the same buffer
view.setInt8(0, 127); // 1 byte at offset 0
view.setInt16(1, 32767); // 2 bytes at offset 1
view.setFloat32(4, 3.14); // 4 bytes at offset 4
// Read the values back
console.log(view.getInt8(0)); // 127
console.log(view.getInt16(1)); // 32767
console.log(view.getFloat32(4)); // 3.14
// Endianness control (default is big-endian)
view.setInt16(0, 256, true); // Little-endian
console.log(view.getInt16(0, true)); // 256
view.setInt16(2, 256, false); // Big-endian
console.log(view.getInt16(2, false)); // 256
Buffer Views - Multiple Views on Same Data
Multiple Typed Arrays can view the same ArrayBuffer:
// Create a 4-byte buffer
const buffer = new ArrayBuffer(4);
// Create different views
const view8 = new Uint8Array(buffer);
const view16 = new Uint16Array(buffer);
const view32 = new Uint32Array(buffer);
// Modify through 8-bit view
view8[0] = 255;
view8[1] = 255;
// Read through 16-bit view (combines two 8-bit values)
console.log(view16[0]); // 65535 (255 * 256 + 255)
// Modify through 32-bit view
view32[0] = 0x12345678;
// Read through 8-bit view (splits into bytes)
console.log(view8); // Uint8Array [120, 86, 52, 18] (little-endian)
// Practical example: Color conversion
const colorBuffer = new ArrayBuffer(4);
const rgba = new Uint8Array(colorBuffer);
const color32 = new Uint32Array(colorBuffer);
// Set RGBA values
rgba[0] = 255; // Red
rgba[1] = 128; // Green
rgba[2] = 64; // Blue
rgba[3] = 255; // Alpha
// Get as single 32-bit value
console.log(color32[0].toString(16)); // RGBA as hex
Practical Use Cases
// Example 1: Reading binary file data
async function readBinaryFile(url) {
const response = await fetch(url);
const buffer = await response.arrayBuffer();
const uint8View = new Uint8Array(buffer);
console.log(`File size: ${buffer.byteLength} bytes`);
console.log(`First 10 bytes:`, uint8View.slice(0, 10));
return uint8View;
}
// Example 2: Image data manipulation
function invertImageColors(imageData) {
const pixels = new Uint8ClampedArray(imageData.data.buffer);
// Iterate through RGBA pixels
for (let i = 0; i < pixels.length; i += 4) {
pixels[i] = 255 - pixels[i]; // Red
pixels[i + 1] = 255 - pixels[i + 1]; // Green
pixels[i + 2] = 255 - pixels[i + 2]; // Blue
// Alpha (i + 3) remains unchanged
}
return imageData;
}
// Example 3: Audio processing
function generateSineWave(frequency, duration, sampleRate = 44100) {
const numSamples = duration * sampleRate;
const buffer = new Float32Array(numSamples);
for (let i = 0; i < numSamples; i++) {
const time = i / sampleRate;
buffer[i] = Math.sin(2 * Math.PI * frequency * time);
}
return buffer;
}
const audioData = generateSineWave(440, 1); // 440 Hz A note, 1 second
console.log(`Generated ${audioData.length} samples`);
// Example 4: Network protocol parsing
function parseHeader(buffer) {
const view = new DataView(buffer);
return {
version: view.getUint8(0),
type: view.getUint8(1),
length: view.getUint16(2),
timestamp: view.getUint32(4),
checksum: view.getUint16(8)
};
}
// Example 5: WebGL vertex buffer
function createVertexBuffer(gl, vertices) {
const buffer = gl.createBuffer();
gl.bindBuffer(gl.ARRAY_BUFFER, buffer);
// Convert to Float32Array for WebGL
const vertexData = new Float32Array(vertices);
gl.bufferData(gl.ARRAY_BUFFER, vertexData, gl.STATIC_DRAW);
return buffer;
}
Converting Between Types
// Typed Array to regular Array
const typed = new Uint8Array([1, 2, 3]);
const regular = Array.from(typed);
const regular2 = [...typed];
// Regular Array to Typed Array
const arr = [10, 20, 30];
const typed2 = new Uint8Array(arr);
const typed3 = Uint8Array.from(arr);
// Typed Array to another Typed Array
const uint8 = new Uint8Array([1, 2, 3]);
const int16 = new Int16Array(uint8);
const float = new Float32Array(uint8);
// ArrayBuffer to Base64 string
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 string 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;
}
Performance Considerations
When to use Typed Arrays:
✓ Processing large amounts of numeric data
✓ Working with binary file formats
✓ WebGL graphics programming
✓ Audio/video processing
✓ Network protocol implementation
✓ High-performance mathematical computations
Performance Benefits:
- Faster access than regular arrays for numeric data
- Less memory overhead
- Better for CPU cache utilization
- Required by many Web APIs (WebGL, Canvas, etc.)
Limitations:
- Fixed size (cannot grow/shrink)
- Only numeric values
- No holes (sparse arrays not possible)
- Less flexible than regular arrays
Practice Exercise:
Challenge: Create a function that converts RGB color values to grayscale.
function rgbToGrayscale(imageData) {
const pixels = new Uint8ClampedArray(imageData.data.buffer);
// Process each pixel (4 bytes: RGBA)
for (let i = 0; i < pixels.length; i += 4) {
const r = pixels[i];
const g = pixels[i + 1];
const b = pixels[i + 2];
// Calculate grayscale using luminosity method
const gray = 0.299 * r + 0.587 * g + 0.114 * b;
// Set RGB to the same grayscale value
pixels[i] = gray;
pixels[i + 1] = gray;
pixels[i + 2] = gray;
// Alpha (i + 3) remains unchanged
}
return imageData;
}
// Test with sample pixel data
const canvas = document.createElement('canvas');
const ctx = canvas.getContext('2d');
const imageData = ctx.createImageData(2, 2);
// Set red pixel
imageData.data[0] = 255; // R
imageData.data[1] = 0; // G
imageData.data[2] = 0; // B
imageData.data[3] = 255; // A
const grayscale = rgbToGrayscale(imageData);
console.log(grayscale.data[0]); // ~76 (grayscale red)
Try it yourself: Create a function to adjust brightness or apply a sepia filter.
Summary
In this lesson, you learned:
- ArrayBuffer is a fixed-size binary data buffer
- Typed Arrays provide typed views into ArrayBuffers
- Multiple Typed Array types exist for different numeric types
- DataView enables flexible reading/writing of mixed data types
- Typed Arrays are essential for WebGL, Canvas, and File APIs
- Value clamping and overflow behavior varies by type
- Multiple views can share the same underlying ArrayBuffer
- Typed Arrays provide better performance for numeric operations
Module Complete! You've completed Module 4: ES6+ Data Structures. Next, we'll dive into Module 5: Object-Oriented JavaScript with ES6 Classes!