TypedArray in TypeScript

TypedArray in TypeScript

This article explains TypedArray in TypeScript.

We will explain TypedArray in TypeScript, including practical examples.

YouTube Video

TypedArray in TypeScript

TypedArray is a mechanism for efficiently handling binary data. It is especially useful for low-level binary operations such as large image data, network byte streams, and numeric arrays for WebGL.

How to create an ArrayBuffer

ArrayBuffer represents a fixed-length region of bytes. First, create a buffer and check its size and byte length.

1// Create an ArrayBuffer of 16 bytes
2const buffer: ArrayBuffer = new ArrayBuffer(16);
3console.log("buffer.byteLength:", buffer.byteLength); // 16
  • This code creates an empty region of 16 bytes. ArrayBuffer itself does not have read/write functions, so you access it through TypedArray or DataView.

Types of TypedArray

There are many types of TypedArray, such as the following. These vary depending on the data type and size they handle.

TypedArray Data Type Bit Size
Int8Array 8-bit integer 8 bits
Uint8Array Unsigned 8-bit integer 8 bits
Uint8ClampedArray Clamped unsigned 8-bit integer 8 bits
Int16Array 16-bit integer 16 bits
Uint16Array Unsigned 16-bit integer 16 bits
Int32Array 32-bit integer 32 bits
Uint32Array Unsigned 32-bit integer 32 bits
Float32Array 32-bit floating-point number 32 bits
Float64Array 64-bit floating-point number 64 bits

Basic TypedArrays (Uint8Array, Int16Array, Float32Array, etc.)

TypedArray creates a 'typed view' on top of an ArrayBuffer. Below are examples of creating and using some typical TypedArrays.

 1// Create a buffer and different typed views over it
 2const buf = new ArrayBuffer(8); // 8 bytes
 3
 4// Create views
 5const u8 = new Uint8Array(buf);    // 8 x uint8
 6const i16 = new Int16Array(buf);   // 4 x int16 (since each int16 is 2 bytes)
 7const f32 = new Float32Array(buf); // 2 x float32 (4 bytes each)
 8
 9console.log("Uint8 length:", u8.length);
10console.log("Int16 length:", i16.length);
11console.log("Float32 length:", f32.length);
  • By creating multiple views on the same ArrayBuffer, you can read and write the same memory in different types or granularities. length of the view is the number of elements, and byteLength is the number of bytes.

Writing and reading (byte-level operations)

When you write a value to a TypedArray, the corresponding bytes in memory are updated. You can see the changes when you read the same buffer with a different view.

 1// Demonstrate writing via one view and reading via another
 2const buffer2 = new ArrayBuffer(4);
 3const u8view = new Uint8Array(buffer2);
 4const u32view = new Uint32Array(buffer2);
 5
 6u8view[0] = 0x78;
 7u8view[1] = 0x56;
 8u8view[2] = 0x34;
 9u8view[3] = 0x12;
10
11console.log("Uint8 bytes:", Array.from(u8view)); // [120, 86, 52, 18]
12console.log("Uint32 value (platform endianness):", u32view[0]); // value depends on endianness
  • In this example, we write a byte sequence and then read the same area as a 32-bit integer. Note that the output depends on the endianness of the execution environment.

Endianness (byte order) and DataView

DataView is useful if you want to control environment-dependent endianness issues. DataView lets you specify endianness when reading and writing.

 1// Use DataView to read/write with explicit endianness control
 2const b = new ArrayBuffer(4);
 3const dv = new DataView(b);
 4
 5// write little-endian 32-bit integer
 6dv.setUint32(0, 0x12345678, true);
 7
 8// read as little-endian and big-endian
 9const little = dv.getUint32(0, true);
10const big = dv.getUint32(0, false);
11
12console.log("little-endian read:", little.toString(16)); // "12345678"
13console.log("big-endian read:", big.toString(16));       // different value
  • DataView is a convenient mechanism for reading and writing bytes in detail. It supports a variety of types, such as signed or unsigned integers and floating-point numbers, and allows you to explicitly specify endianness (byte order), which is very useful when implementing binary protocols.

Difference between subarray and slice

subarray of a TypedArray returns a view sharing the original buffer, while slice returns a new copy. Performance and side effects differ depending on which you use.

1const base = new Uint8Array([1, 2, 3, 4, 5]);
2
3const shared = base.subarray(1, 4); // shares underlying buffer
4const copied = base.slice(1, 4);    // copies data
5
6shared[0] = 99;
7console.log("base after shared modification:", base); // shows change
8console.log("copied remains:", copied);              // unaffected
  • If you modify the shared view, the original array will also change, which may cause unintended side effects. If you want to safely preserve the original array, you can create a copy in advance using slice().

Copying buffers and type conversion (conversion between TypedArrays)

We will explain how to copy data between TypedArrays and how to paste parts using the set method.

 1// Copy and convert between typed arrays
 2const src = new Float32Array([1.5, 2.5, 3.5]);
 3const dst = new Uint16Array(src.length);
 4
 5// Convert by mapping (explicit conversion)
 6for (let i = 0; i < src.length; i++) {
 7  dst[i] = Math.round(src[i]); // simple conversion rule
 8}
 9console.log("converted dst:", Array.from(dst)); // [2, 2, 4]
10
11// Using set to copy bytes (requires compatible underlying buffer or same element type)
12const src2 = new Uint8Array([10, 20, 30]);
13const dst2 = new Uint8Array(6);
14dst2.set(src2, 2); // copy src2 into dst2 starting at index 2
15console.log("dst2 after set:", Array.from(dst2)); // [0,0,10,20,30,0]
  • If the element types differ, you need to convert values, such as rounding or range checking, rather than simply copying. set allows fast copying between TypedArrays of the same element type.

Practical example: A binary protocol parser (simple implementation)

Here, we introduce a simple parser example which reads a fixed-format binary data consisting of a 1-byte type, 2-byte data length, and a following payload.

 1// Simple binary message parser:
 2// format: [type: uint8][length: uint16 BE][payload: length bytes]
 3function parseMessages(buffer: ArrayBuffer) {
 4  const dv = new DataView(buffer);
 5  let offset = 0;
 6  const messages: { type: number; payload: Uint8Array }[] = [];
 7
 8  while (offset + 3 <= dv.byteLength) {
 9    const type = dv.getUint8(offset);
10    const length = dv.getUint16(offset + 1, false); // big-endian
11    offset += 3;
12    if (offset + length > dv.byteLength) break; // truncated
13    const payload = new Uint8Array(buffer, offset, length);
14    messages.push({ type, payload });
15    offset += length;
16  }
17
18  return messages;
19}
20
21// Example usage
22const buf = new ArrayBuffer(1 + 2 + 3 + 1 + 2 + 2); // two messages
23const dv = new DataView(buf);
24let off = 0;
25// first message: type=1, length=3, payload=[1,2,3]
26dv.setUint8(off, 1); dv.setUint16(off + 1, 3, false); off += 3;
27new Uint8Array(buf, off, 3).set([1, 2, 3]); off += 3;
28// second message: type=2, length=2, payload=[9,8]
29dv.setUint8(off, 2); dv.setUint16(off + 1, 2, false); off += 3;
30new Uint8Array(buf, off, 2).set([9, 8]);
31
32console.log(parseMessages(buf));
  • In this example, the header is read using DataView, and the payload slice is created with Uint8Array. Endianness and buffer length checking are important.

Web API and TypedArray (Example: fetching binary data)

This is a typical example of handling an ArrayBuffer obtained from a network request using TypedArray.

 1// Example of fetching binary and mapping to typed array
 2async function fetchBinary(url: string) {
 3  const res = await fetch(url);
 4  const ab = await res.arrayBuffer();
 5  const view = new Uint8Array(ab);
 6  // process view...
 7  console.log("received bytes:", view.length);
 8  return view;
 9}
10
11// Usage (call in async context)
12// fetchBinary("/path/to/file.bin");
  • You can directly pass the ArrayBuffer obtained with Response.arrayBuffer() to a TypedArray. It is used for images, audio, or custom binary protocols.

Performance tips and common pitfalls

Here are some 'performance tips' and 'common pitfalls' that are helpful to know when using TypedArrays:.

  • Avoid unnecessary copying To process large data efficiently, you can reduce unnecessary copying by creating partial views with subarray or sharing the same ArrayBuffer across multiple views.

  • Be careful with endianness For network communication or file formats, the ordering of data (byte order) is often specified. Using DataView allows you to specify the endianness explicitly when reading and writing, preventing unintended misinterpretation.

  • Be aware of value ranges for each type For example, Uint8 can only represent values from 0 to 255. If you input a negative value, truncation or value rollover may occur, so you should define conversion rules as needed.

  • Consider stress on garbage collection Frequently recreating large ArrayBuffers increases the burden of memory management. In performance-critical situations, you may consider designing the code to reuse existing buffers as much as possible.

Summary

TypedArray is a mechanism for handling binary data quickly and efficiently. By combining ArrayBuffer, which reserves a fixed-length byte area, with TypedArray or DataView, which read and write the contents using specific types, you can perform flexible binary operations. Depending on your use case, you can choose between using subarray/slice or DataView, and design your implementation with attention to endianness and copies.

You can follow along with the above article using Visual Studio Code on our YouTube channel. Please also check out the YouTube channel.

YouTube Video