Lesson 16 / 17
Typed Arrays
Fixed-size binary buffers and the typed views over them, overflow and clamping behavior, explicitly specifying byte order, and how typed arrays differ from ordinary arrays.
Contents
All the collections in this topic have held arbitrary JavaScript values. When a measurement stream needs to be written to a file or sent over a network, though, the data has to be represented in a fixed layout that fits a specific number of bytes.
Typed arrays exist for this job. Two things work together: ArrayBuffer, which holds
raw bytes, and a view, which interprets those bytes as a specific number type. The
representations established in the Signed Integers, Floating-Point Numbers, and Byte
Order lessons of the How Computers Work course become directly observable here.
Buffer and View
ArrayBuffer holds only bytes; its content cannot be accessed directly. A view is set up
over it for access, and the view’s type determines how those same bytes are read.
const buffer = new ArrayBuffer(16); console.log(buffer.byteLength); const byteView = new Uint8Array(buffer); const floatView = new Float64Array(buffer); console.log(byteView.length); console.log(floatView.length); console.log(Uint8Array.BYTES_PER_ELEMENT); console.log(Float64Array.BYTES_PER_ELEMENT); floatView[0] = 21.4; console.log(floatView[0]); console.log([...byteView.slice(0, 8)].join(",")); byteView[0] = 0; console.log(floatView[0]); console.log(byteView.buffer === floatView.buffer); const standalone = new Float64Array(2); console.log(standalone.buffer === buffer); console.log(standalone[0]);
16 16 2 1 8 21.4 102,102,102,102,102,102,53,64 21.399999999999636 true false 0
The same sixteen bytes are sixteen integers in one view, two decimal numbers in the other. Both views look at the same memory: the bytes of a value written to the float view are read in the byte view, and a single change made in the byte view corrupts the decimal value. The deviation in the eighth line is the floating-point representation’s counterpart of the least significant byte being zeroed out.
Byte layout depends on the platform’s native byte order; the output above was produced on a little-endian machine. The same program would show a different layout on a big-endian machine. How to achieve portability is the subject of this lesson’s third section.
A view set up without a given buffer creates its own buffer and starts filled with zeros.
Overflow and Clamping
A typed array’s slot cannot hold a value outside the range its type allows. What happens to out-of-range values differs by type.
const wrapping = new Uint8Array(4); const clamping = new Uint8ClampedArray(4); const signed = new Int8Array(4); const attempts = [300, -1, 255.9, 128]; attempts.forEach((value, i) => { wrapping[i] = value; clamping[i] = value; signed[i] = value; }); console.log([...wrapping].join(",")); console.log([...clamping].join(",")); console.log([...signed].join(",")); const integer = new Int32Array(1); integer[0] = 21.9; console.log(integer[0]); const decimal = new Float32Array(1); decimal[0] = 21.4; console.log(decimal[0]); console.log(decimal[0] === 21.4); const doublePrecision = new Float64Array(1); doublePrecision[0] = 21.4; console.log(doublePrecision[0] === 21.4);
44,255,255,128 255,0,255,128 44,-1,-1,-128 21 21.399999618530273 false true
Uint8Array wraps the value around 256: 300 becomes 44, -1 becomes 255.
Uint8ClampedArray clamps to the range instead: 300 stops at 255, -1 stops at 0.
Int8Array interprets the same bytes as signed; 128 is read as -128 — the direct result
of two’s complement representation.
Integer views drop the fractional part, no rounding is done: 21.9 becomes 21. The clamped type, though, rounds to the nearest integer: 255.9 becomes 256 and is then clamped to the range, staying at 255.
The last three lines show the precision difference. Float32Array stores the number in
single precision and the value read back differs from the original; Float64Array uses
JavaScript numbers’ own representation, so it preserves the value exactly. In binary
record formats, the choice of field width directly determines this trade-off.
Explicitly Specifying Byte Order
Typed-array views use the native byte order; this causes problems when the data needs to
be read on another machine. DataView lets byte order be explicitly specified on every
read and write.
Consider placing measurement records into a fixed twelve-byte layout: four bytes for the sensor number, eight bytes for the measured value.
const RECORD_SIZE = 12; function writeRecords(records, littleEndian) { const buffer = new ArrayBuffer(RECORD_SIZE * records.length); const view = new DataView(buffer); records.forEach((record, i) => { const offset = i * RECORD_SIZE; view.setUint32(offset, record.sensorId, littleEndian); view.setFloat64(offset + 4, record.value, littleEndian); }); return buffer; } function readRecords(buffer, littleEndian) { const view = new DataView(buffer); const result = []; for (let offset = 0; offset < buffer.byteLength; offset += RECORD_SIZE) { result.push({ sensorId: view.getUint32(offset, littleEndian), value: view.getFloat64(offset + 4, littleEndian), }); } return result; } const records = [ { sensorId: 1, value: 21.4 }, { sensorId: 2, value: 19.8 }, ]; const littleEndianBuffer = writeRecords(records, true); const bigEndianBuffer = writeRecords(records, false); console.log(littleEndianBuffer.byteLength); console.log([...new Uint8Array(littleEndianBuffer).slice(0, 4)].join(",")); console.log([...new Uint8Array(bigEndianBuffer).slice(0, 4)].join(",")); console.log(JSON.stringify(readRecords(littleEndianBuffer, true))); console.log(JSON.stringify(readRecords(bigEndianBuffer, false))); console.log(readRecords(bigEndianBuffer, true)[0].sensorId);
24
1,0,0,0
0,0,0,1
[{"sensorId":1,"value":21.4},{"sensorId":2,"value":19.8}]
[{"sensorId":1,"value":21.4},{"sensorId":2,"value":19.8}]
16777216
The two buffers carry the same records but their bytes are laid out in reverse order.
This output is platform-independent: because DataView explicitly takes the order on
write and read, it does not look at the machine’s native order at all.
The last line shows the cost of a wrong assumption. Data written as big-endian, when read
as little-endian, gives 16777216 instead of 1; no error is thrown, the number just
becomes meaningless. The network-byte-order discussion in the Computer Networks
curriculum is the same problem’s counterpart at the protocol level: when a binary format
is defined, byte order has to be written as part of the format.
Differences from an Ordinary Array
Typed arrays are not a subtype of the Array type. Their prototype chains are separate,
and they do not carry some array behaviors.
const typed = new Float64Array([21.4, 19.8, 25.1]); const ordinary = [21.4, 19.8, 25.1]; console.log(Array.isArray(typed)); console.log(Array.isArray(ordinary)); console.log(typeof typed.push); console.log(typeof ordinary.push); typed[5] = 99; console.log(typed.length); console.log(typed[5]); const superPrototype = Object.getPrototypeOf(Float64Array.prototype); console.log(superPrototype === Object.getPrototypeOf(Uint8Array.prototype)); console.log(superPrototype === Array.prototype); console.log(Object.getPrototypeOf(superPrototype) === Object.prototype); const mapped = typed.map((d) => d * 2); console.log(mapped.constructor === Float64Array); console.log([...mapped].join(",")); console.log([...typed].join(",")); console.log(JSON.stringify(typed)); console.log(JSON.stringify(Array.from(typed)));
false
true
undefined
function
3
undefined
true
false
true
true
42.8,39.6,50.2
21.4,19.8,25.1
{"0":21.4,"1":19.8,"2":25.1}
[21.4,19.8,25.1]
Length is fixed: writing out of bounds is silently ignored, growing methods like push
are not present. This is the language’s counterpart of the Arrays vs. Dynamic Arrays
distinction from the Data Structures course — a typed array is the first, an ordinary
array the second.
The chain structure is separate too. All typed-array types share a common intermediate
prototype; methods like map, filter, reduce are defined there and have no
connection to Array.prototype. The intermediate prototype’s prototype is
Object.prototype directly. map returning a typed array again is also the result of
this separate implementation.
The difference in serialization is clear: JSON.stringify writes a typed array as an
object with numeric keys, not as an array. If binary data needs to be carried in text
form, it has to be converted to an ordinary array first.
View or Copy
Two methods working on the same buffer establish a distinction that connects directly to
this course’s last lesson: subarray returns a view, slice returns a copy.
const allMeasurements = new Float64Array([21.4, 19.8, 25.1, 18.2, 30.0]); const windowView = allMeasurements.subarray(1, 4); const windowCopy = allMeasurements.slice(1, 4); console.log([...windowView].join(",")); console.log([...windowCopy].join(",")); console.log(windowView.buffer === allMeasurements.buffer); console.log(windowCopy.buffer === allMeasurements.buffer); windowView[0] = 0; windowCopy[1] = 0; console.log([...allMeasurements].join(",")); console.log([...windowCopy].join(",")); console.log(windowView.byteOffset); console.log(windowCopy.byteOffset); console.log(allMeasurements.byteLength);
19.8,25.1,18.2 19.8,25.1,18.2 true false 21.4,0,25.1,18.2,30 19.8,0,18.2 8 0 40
The two windows show the same values at the start. Writing to the view changes the original array, writing to the copy does not. They differ in memory too: the view allocates no new bytes and starts at an offset inside the original buffer; the copy creates its own buffer.
Using a view for windowing operations on large measurement blocks allows working without duplicating the data. In exchange, because a view keeps the original buffer reachable, a small window can keep a large buffer alive in memory — another form of the reachability discussion from the Weak Collections lesson.
Summary
ArrayBufferholds raw bytes; access is done through views that interpret the bytes as a specific number type.- More than one view can be set up over the same buffer, and all of them share the same memory.
- Integer views wrap out-of-range values, the clamped type clamps to the range; the fractional part is dropped in integer types.
- Typed-array views use the native byte order; for portable binary formats, order is
explicitly specified with
DataView. - Typed arrays have a chain separate from the
Arraytype: their length is fixed, they cannot be grown, and they are serialized as objects. subarrayreturns a view looking at the same memory,slicereturns an independent copy.
Next Step
In this lesson, the copy–view distinction showed up at the byte level; the same question
has been open for objects since the start of the course. Object spread lost the chain,
Object.assign turned accessors into plain values, JSON.stringify dropped methods and
Map content. The last lesson takes up this distinction in full: what gets carried and
what gets dropped when copying an object, and by which paths — with which losses — a deep
copy is done.
To keep your progress and take notes, Log in
My notes
Log in to take notes.