ArrayBuffer in TypeScript
This article explains ArrayBuffer in TypeScript.
We will explain ArrayBuffer in TypeScript step-by-step, covering basics to practical techniques.
YouTube Video
ArrayBuffer in TypeScript
ArrayBuffer is a built-in object that represents a “raw memory area” for binary data. It represents a raw buffer of fixed length, upon which TypedArray or DataView are layered for reading and writing.
Basic Concepts and Characteristics of ArrayBuffer
ArrayBuffer is a fixed-length byte sequence. You specify the size in bytes when creating it, and its length cannot be changed afterward.
1// Create a new ArrayBuffer of 16 bytes.
2const buf = new ArrayBuffer(16);
3console.log(buf.byteLength); // 16
- This code creates an empty buffer of 16 bytes. You can check the size using
byteLength.
TypedArray and DataView — Views for Manipulating Buffers
ArrayBuffer does not have the ability to read or write data. Therefore, actual operations are performed through TypedArray or DataView, specifying types such as integers or floating-point numbers and endianness when accessing data.
1// Create a buffer and a Uint8Array view over it. Then write bytes and read them.
2const buffer = new ArrayBuffer(8);
3const u8 = new Uint8Array(buffer);
4
5u8[0] = 0x41; // 'A'
6u8[1] = 0x42; // 'B'
7console.log(u8); // Uint8Array(8) [ 65, 66, 0, 0, 0, 0, 0, 0 ]
Uint8Arrayis a byte-wise array view, and can be accessed like a normal array. If you place multiple views on the sameArrayBuffer, they share the same memory for reading and writing.
DataView: Reading and Writing at Arbitrary Boundaries and Endianness
DataView is useful for fine-grained reading and writing by byte, and allows you to specify little or big endian.
1// Using DataView to write/read multi-byte values with endianness control.
2const buf2 = new ArrayBuffer(8);
3const view = new DataView(buf2);
4
5// Write a 32-bit integer (little-endian)
6view.setInt32(0, 0x12345678, true);
7
8// Read it back as little-endian and big-endian
9const little = view.getInt32(0, true);
10const big = view.getInt32(0, false);
11console.log(little.toString(16)); // "12345678"
12console.log(big.toString(16)); // "78563412"
DataViewallows you to read and write values by specifying offsets in memory, making it suitable for implementing protocols that require handling network byte order.
Conversion between Strings and ArrayBuffer (TextEncoder / TextDecoder)
To convert text to binary and vice versa, use TextEncoder and TextDecoder.
1// Convert string -> ArrayBuffer and back using TextEncoder/TextDecoder.
2const encoder = new TextEncoder();
3const decoder = new TextDecoder();
4
5// Unicode escape sequences
6const str = "\u3053\u3093\u306B\u3061\u306F";
7const encoded = encoder.encode(str); // Uint8Array
8console.log(encoded); // Uint8Array([...])
9
10// If you need an ArrayBuffer specifically:
11const ab = encoded.buffer.slice(encoded.byteOffset, encoded.byteOffset + encoded.byteLength);
12console.log(ab.byteLength); // bytes length of encoded text
13
14// Decode back
15const decoded = decoder.decode(encoded);
16console.log(decoded);TextEncoder.encodereturns aUint8Array, so if you need anArrayBuffer, you should refer to its.bufferproperty. With theslicemethod, you can extract the required data by specifying the offset and length.
Slicing and Copying ArrayBuffer
The slice method returns a new ArrayBuffer. ArrayBuffer cannot be resized; if resizing is necessary, create a new buffer and copy the data into it.
1// Slice an ArrayBuffer and copy to a new sized buffer.
2const original = new Uint8Array([1,2,3,4,5]).buffer;
3const part = original.slice(1, 4); // bytes 1..3
4console.log(new Uint8Array(part)); // Uint8Array [ 2, 3, 4 ]
5
6// Resize: create a new buffer and copy existing content
7const larger = new ArrayBuffer(10);
8const target = new Uint8Array(larger);
9target.set(new Uint8Array(original), 0);
10console.log(target); // first bytes filled with original data
- Since the
slicemethod returns a newArrayBuffer, if you prioritize efficiency or sharing references, you can use thesubarraymethod ofTypedArrayto refer to the same buffer.
Difference between TypedArray.subarray and Copying
TypedArray's subarray returns a new view that refers to the same ArrayBuffer.
1// subarray shares the same underlying buffer; modifying one affects the other.
2const arr = new Uint8Array([10,20,30,40]);
3const viewSub = arr.subarray(1,3); // shares memory
4viewSub[0] = 99;
5console.log(arr); // Uint8Array [10, 99, 30, 40]
- Shared views can avoid the cost of copying, but since they refer to the same buffer, you need to be careful about side effects.
Concatenating Buffers (Combining Multiple ArrayBuffers)
Since ArrayBuffer is of fixed length, to combine multiple buffers create a new ArrayBuffer and copy the data.
1// Concatenate multiple ArrayBuffers
2function concatBuffers(buffers: ArrayBuffer[]): ArrayBuffer {
3 const total = buffers.reduce((sum, b) => sum + b.byteLength, 0);
4 const result = new Uint8Array(total);
5 let offset = 0;
6 for (const b of buffers) {
7 const u8 = new Uint8Array(b);
8 result.set(u8, offset);
9 offset += u8.length;
10 }
11 return result.buffer;
12}
13
14const a = new Uint8Array([1,2]).buffer;
15const b = new Uint8Array([3,4,5]).buffer;
16const c = concatBuffers([a, b]);
17console.log(new Uint8Array(c)); // [1,2,3,4,5]
- If you frequently concatenate large amounts of data, precomputing the total size and allocating a new buffer only once, as in this code, is more efficient.
Passing ArrayBuffer to a Worker (Transferable)
In the browser's postMessage, you can transfer an ArrayBuffer as a "transferable" object. Ownership can be transferred without copying, which avoids the cost of copying.
1// Example: posting an ArrayBuffer to a Worker as a transferable object (browser)
2const worker = new Worker('worker.js');
3const bufferToSend = new Uint8Array([1,2,3,4]).buffer;
4
5// Transfer ownership to the worker (main thread no longer owns it)
6worker.postMessage(bufferToSend, [bufferToSend]);
7
8// After transfer, bufferToSend.byteLength === 0 in many browsers (detached)
9console.log(bufferToSend.byteLength); // may be 0
- By specifying the objects to transfer in an array as the second argument of
postMessage, you can transfer ownership, without copying, of transferable objects such asArrayBuffer. - After transferring, the buffer becomes "detached" on the original side and cannot be accessed. Using
SharedArrayBufferallows simultaneous access from multiple threads, but its use comes with security requirements and environment restrictions.
Handling in Node.js (Interconversion with Buffer)
In Node.js, you can convert between the Node.js binary type Buffer and ArrayBuffer. This is useful when targeting both browsers and Node.js with TypeScript.
1// Convert ArrayBuffer <-> Node.js Buffer
2// In Node.js environment:
3const ab = new Uint8Array([10,20,30]).buffer;
4const nodeBuffer = Buffer.from(ab); // ArrayBuffer -> Buffer
5console.log(nodeBuffer); // <Buffer 0a 14 1e>
6
7const backToAb = nodeBuffer.buffer.slice(
8 nodeBuffer.byteOffset,
9 nodeBuffer.byteOffset + nodeBuffer.byteLength
10);
11console.log(new Uint8Array(backToAb)); // Uint8Array [10,20,30]
Buffer.from(arrayBuffer)in Node.js usually creates a copy, but there are cases where reference sharing is possible, so be mindful of offsets.
Performance Considerations and Best Practices
To optimize performance and handle ArrayBuffer efficiently, there are several important points to keep in mind. Below, we list and explain practical best practices.
-
Reduce the Number of Copies When handling large binaries, use
subarray(shared view) or transferables to reduce copies. -
Allocate Large Buffers at Once Allocating small buffers repeatedly increases overhead. If possible, allocate a large buffer at once and use portions of it as needed.
-
Specify Endianness Explicitly When handling multibyte values, use
DataViewand explicitly specify endianness. Big-endian is often standard for network protocols.
Common Use Case Examples
ArrayBuffer is widely used both in browsers and Node.js.
- Parsing and building binary protocols (processing header information with
DataView) - Media processing such as image and audio data (
fetch(...).then(res => res.arrayBuffer())) - Shared memory for WebAssembly (Wasm memory operates based on
ArrayBuffer) - Offloading heavy processing to Workers (passing
ArrayBufferas transferable)
The following code is an example of acquiring and analyzing binary data.
1// Example: fetch binary data in browser and inspect first bytes
2async function fetchAndInspect(url: string) {
3 const resp = await fetch(url);
4 const ab = await resp.arrayBuffer();
5 const u8 = new Uint8Array(ab, 0, Math.min(16, ab.byteLength));
6 console.log('first bytes:', u8);
7}- This code fetches binary data from any URL in the browser and displays the first few bytes. The code loads data fetched via the
fetchAPI as anArrayBufferand inspects the first 16 bytes with aUint8Array.
Summary
ArrayBuffer is a raw memory representation, enabling efficient binary operations via TypedArray and DataView. By designing to avoid unnecessary copying and explicitly specifying endianness, you can achieve safe and high-performance binary processing.
You can follow along with the above article using Visual Studio Code on our YouTube channel. Please also check out the YouTube channel.