preface

Binary arrays are an interface that JavaScript uses to manipulate binary data. There are three object interfaces: ArrayBuffer, TypedArray view and DataView. They can manipulate memory directly in the form of array subscripts and can communicate binary with the operating system’s native interface.

As Web applications become more powerful, especially with new additions such as: Audio and video editing, accessing WebSockets raw data, etc. Obviously, sometimes it would be very helpful to have JavaScript code that can quickly and easily manipulate raw binary data through Typed Arrays. Note :(do not confuse typed arrays with normal arrays. Calling array.isarray (arr) on typed arrays returns false. Not all methods available on normal arrays are supported by typed arrays.)

Type array architecture: buffers and views

JavaScript Typed Arrays split the implementation into buffers and views. It is a special Array that handles binary data, manipulating bytes as C does, but only after an ArrayBuffer object is created and mapped to a view of the specified format. There are two types of views, a specific type of TypedArray and a generic type of DataView. The introduction of typed arrays in ES6 has greatly improved the performance of JavaScript math.

ArrayBuffer object: Represents a chunk of binary data in memory that can be manipulated by view. The View deploys an array interface, which means that memory can be manipulated as an array.

TypedArray object: A view used to generate memory, with nine constructors that can generate views in nine data formats

DataView object: A view used to generate memory, with custom formatting and byte order

In simple terms, ArrayBuffer objects represent raw binary data, TypedArray objects represent binary data of determined type, and DataView objects represent binary data of uncertain type. They support a total of nine data types (DataView objects support eight other data types besides Unit8c)


Ii. ArrayBuffer

Although an ArrayBuffer can create a fixed-size memory area (that is, an ArrayBuffer), it cannot read or write directly to the data it stores and requires views (TypeArray and DataView) to do so.

【 2.1 】 ArrayBuffer ()

The constructor ArrayBuffer() allocates a buffer of a specified number of bytes, as shown in the code below, which allocates an 8-byte memory region, each with a default value of 0. It is important to note that the size of the buffer cannot be changed once specified.

let buffer = new ArrayBuffer(8);
Copy the code

【 2.2 】 ArrayBuffer. Prototype. ByteLength

The byteLength property of an ArrayBuffer instance that returns the size of the allocated memory region in bytes.

Sometimes the amount of memory we need to allocate is so large that the allocation may fail (because there is not that much contiguous free memory), so it is necessary to check if the allocation is successful.

let buffer = new ArrayBuffer(8)
console.log(buffer.byteLength) / / 8Copy the code

【 2.3 】 ArrayBuffer. Prototype. Slice ()

An ArrayBuffer instance has a slice method that allows a new ArrayBuffer object to be copied from a portion of the memory area.

let buffer = new ArrayBuffer(8);
let newBuffer = buffer.slice(0.3);

// The above code copies the first three bytes of the buffer (starting at 0 and ending before the third byte) to generate a new ArrayBuffer.
// The slice method actually consists of two steps. The first step is to allocate a new memory and the second step is to copy the original ArrayBuffer object.Copy the code

【 2.4 】 ArrayBuffer. IsView ()

ArrayBuffer has a static method, isView, that returns a Boolean value indicating whether the arguments are view instances of ArrayBuffer. This method is roughly equivalent to determining whether the parameter is an instance of TypedArray or DataView.

let buffer = new ArrayBuffer(8);
ArrayBuffer.isView(buffer) // false

let v = new Int32Array(buffer);
ArrayBuffer.isView(v) // trueCopy the code

Third, TypedArray

An ArrayBuffer serves as an area of memory that can store various types of data. Different data in the same memory can be interpreted in different ways, which is called a view. ArrayBuffer has two types of views, one is TypedArray view and the other is DataView. The difference between the two is mainly the byte order, the former array members are the same data type, the latter array members can be different data types. Currently, TypedArray objects provide a total of nine types of views, each of which is a constructor.

[3.1] Data type

  • Int8Array: an 8-bit signed integer, 1 byte in length
  • Uint8Array: 8-bit unsigned integer, 1 byte in length
  • Uint8ClampedArray: 8-bit unsigned integer, length 1 byte, different overflow handling
  • Int16Array: a 16-bit signed integer of 2 bytes
  • Uint16Array: 16-bit unsigned integer, length of 2 bytes
  • Int32Array: a 32-bit signed integer of 4 bytes
  • Uint32Array: a 32-bit unsigned integer of 4 bytes
  • Float32Array: a 32-bit floating-point number of 4 bytes
  • Float64Array: a 64-bit floating-point number of 8 bytes

The objects generated by these nine constructors are collectively called TypedArray objects. They all have the length attribute, the square bracket operator ([]) can be used to get individual elements, and all array methods can be used on typed arrays.

The main differences between TypeArray and arrays are:

  • All members of the TypedArray array are of the same type and format.
  • The members of a TypedArray array are contiguous and do not have vacancies.
  • The default value of a Typed array member is 0. For example, new Array(10) returns a normal Array with no members, just 10 empty Spaces; New Unit8Array(10) returns a typed array with all 10 members 0.
  • A TypedArray array is only a layer view and does not store data itself. Its data is stored in the underlying ArrayBuffer object, which must be accessed using the Buffer property.

【 3.2 】TypedArray(buffer, byteOffset=0, length?)


  • First argument (required) : the underlying ArrayBuffer object corresponding to the view,
  • Second argument (optional) : the byte number from which the view starts, starting at 0 by default.
  • Third parameter (optional) : the number of views to contain, default until the end of the memory area.
// Create an 8-byte ArrayBuffer
let b = new ArrayBuffer(8);

// Create an Int32 view pointing to b, starting at byte 0 and ending at the end of the buffer
let v1 = new Int32Array(b);

// Create a Uint8 view pointing to B, starting at byte 2 and ending at the end of the buffer
let v2 = new Uint8Array(b, 2);

// Create an Int16 view to b, starting with byte 2 and of length 2
let v3 = new Int16Array(b, 2.2);


// v1[0] is a 32-bit integer pointing to bytes 0 to 3;
// v2[0] is an 8-bit unsigned integer pointing to byte 2;
V3 [0] is a 16-bit integer pointing to bytes 2 to 3.
V1, v2, and v3 are overlapped, and any memory changes made by one view are reflected in the other two views.Copy the code

【 3.3 】 TypedArray (length)

Views can also be generated by allocating memory directly without using an ArrayBuffer object.

let f64a = new Float64Array(8);
f64a[0] = 10;
f64a[1] = 20;
f64a[2] = f64a[0] + f64a[1];

// Generate a Float64Array array of 8 members (64 bytes)
// Then assign values to each member in turn. In this case, the view constructor takes the number of members
// As you can see, the assignment of a view array is no different from that of a normal arrayCopy the code

【 3.4 】 TypedArray (typeArray)

The constructor of a typed array that can take another view instance as an argument.

let typedArray = new Int8Array(new Uint8Array(4));

// In the code above, the Int8Array constructor takes an instance of the Uint8Array as an argument.
// Note that the generated array only copies the values of the parameter array, and the underlying memory is different. The new array will create a new memory to store the data, and will not build views on the memory of the original array.


let x = new Int8Array([1.1]);
let y = new Int8Array(x);
x[0] / / 1
y[0] / / 1
x[0] = 2;
y[0] / / 1

// In the code above, array Y is generated from array x. When x changes, y does not change.
// If you want to construct different views based on the same memory segment, you can use the following syntax.


var x = new Int8Array([1.1]);
var y = new Int8Array(x.buffer);
x[0] / / 1
y[0] / / 1
x[0] = 2;
y[0] / / 2Copy the code

【 3.5 】 TypedArray (arrayLikeObject)

The constructor can also take an ordinary array and generate a TypedArray instance directly.

let typedArray = new Uint8Array([1.2.3.4]);

// The above code generates a TypedArray instance of an 8-bit unsigned integer from an ordinary array
// Note that the TypedArray view is recreated in memory and does not create views on the original array's memory
// TypedArray arrays can also be converted back to normal arrays

let normalArray = Array.prototype.slice.call(typedArray);Copy the code

[3.6] BYTES_PER_ELEMENT attribute

The constructor of each view has a BYTES_PER_ELEMENT property that represents the number of bytes that the data type occupies

Int8Array.BYTES_PER_ELEMENT / / 1
Uint8Array.BYTES_PER_ELEMENT / / 1
Int16Array.BYTES_PER_ELEMENT / / 2
Uint16Array.BYTES_PER_ELEMENT / / 2
Int32Array.BYTES_PER_ELEMENT / / 4
Uint32Array.BYTES_PER_ELEMENT / / 4
Float32Array.BYTES_PER_ELEMENT / / 4
Float64Array.BYTES_PER_ELEMENT / / 8

/ / this property can also get on TypedArray instance, namely a TypedArray. Prototype. BYTES_PER_ELEMENTCopy the code

[3.7] ArrayBuffer and string conversion

The conversion of an ArrayBuffer to a string, or a string to an ArrayBuffer, assumes that the encoding of the string is determined. Assuming the string is encoded in UTF-16 (JavaScript’s internal encoding), you can write your own conversion function.

// ArrayBuffer is converted to a string. The parameter is an ArrayBuffer object
function ab2str(buffer) {
  return String.fromCharCode.apply(null.new Uint16Array(buffer));
}

// The string is converted to an ArrayBuffer object
function str2ab(str) {
  let buf = new ArrayBuffer(str.length * 2); // Each character takes 2 bytes
  let bufView = new Uint16Array(buf);
  for (var i = 0, strLen = str.length; i < strLen; i++) {
    bufView[i] = str.charCodeAt(i);
  }
  return buf;
}Copy the code

[3.8] overflow

The range of values that can be accommodated by different view types is determined. Beyond this range, an overflow occurs. For example, an 8-bit view can only hold one 8-bit binary value, and if you put a 9-bit value in it, it will overflow. TypedArray array overflow rules, in a nutshell, discard overflow bits and interpret them according to the view type.

let uint8 = new Uint8Array(1); // 1 byte = 8 bits

uint8[0] = 256;
uint8[0] / / 0

uint8[0] = - 1;
uint8[0] / / 255

// In the code above, uint8 is an 8-bit view,
// The binary form of 256 is a nine-bit value of 100000000, which is when an overflow occurs.
// According to the rule, only the last 8 bits, i.e. 00000000, will be reserved. The Uint8 view is interpreted as an unsigned 8-bit integer, so 00000000 is 0.Copy the code

【 3.9 】 TypedArray. Prototype. Buffer

TypedArray instance’s Buffer property, which returns an ArrayBuffer object corresponding to the entire memory region. This property is read-only.

let a = new Float32Array(64);
let b = new Uint8Array(a.buffer);

// View A and view B correspond to the same ArrayBuffer, i.e. the same memory segment.Copy the code

【 3.10 】 TypedArray. Prototype. ByteLength, TypedArray. Prototype. ByteOffset

The byteLength property returns the length of memory occupied by the TypedArray array, in bytes. The byteOffset property returns what byte of the underlying ArrayBuffer object the TypedArray array starts from. Both properties are read-only.

var b = new ArrayBuffer(8);

var v1 = new Int32Array(b);
var v2 = new Uint8Array(b, 2);
var v3 = new Int16Array(b, 2.2);

v1.byteLength / / 8
v2.byteLength / / 6
v3.byteLength / / 4

v1.byteOffset / / 0
v2.byteOffset / / 2
v3.byteOffset / / 2Copy the code

【 3.11 】 TypedArray. Prototype. Length

The Length property indicates how many members the TypedArray array contains. Note the distinction between the byteLength attribute, which is byteLength, and the length attribute, which is member length.

let a = new Int16Array(8);

a.length / / 8
a.byteLength / / 16Copy the code

【 3.12 】 TypedArray. Prototype. The set ()

The set method of a TypedArray array is used to copy an array (either a normal array or a TypedArray array), that is, to copy one segment of memory exactly to another.

var a = new Uint8Array(8);
var b = new Uint8Array(8);
b.set(a);

// The above code copies the contents of array A to array B. It copies the entire memory, much faster than copying each member individually.
The set method can also take a second argument, which member of the B object to copy from.

var a = new Uint16Array(8);
var b = new Uint16Array(10);
b.set(a, 2)

// The array b has two more members than the array A, so copy from b[2].Copy the code

【 3.13 】 TypedArray. Prototype. Subarray ()

The subArray method creates a new view for part of the TypedArray array. The first argument to the subarray method is the starting member number, the second argument is the ending member number (excluding that member), and if omitted contains all remaining members.

var a = new Uint16Array(8);
var b = a.subarray(2.3);

a.byteLength / / 16
b.byteLength / / 2

// a.subbarray (2,3) of the code above means that b contains only one member, a[2], with a length of 2 bytes.Copy the code

【 3.14 】 TypedArray. Prototype. Slice ()

The Slice method of a TypeArray instance that returns a new TypedArray instance at a specified location. The slice method argument, representing the location of the original array, starts generating a new array. Negative values indicate the reverse position, i.e. -1 is the first to last position, -2 is the second to last position, and so on.

let ui8 = Uint8Array.of(0.1.2); / / [0, 1, 2]
ui8.slice(- 1) // Uint8Array [2]

// In the code above, UI8 is an instance of an 8-bit unsigned integer array view.
Its slice method returns a new view instance from the current view.Copy the code

Fourth, the DataView

If a piece of data contains multiple types (such as HTTP data from a server), you can operate from the DataView view in addition to creating a composite view of an ArrayBuffer object.

The DataView provides more action options and support for setting byte order. Originally, the various TypedArray views of ArrayBuffer objects were designed to transmit data to native devices such as network cards and sound cards, so the native byte order would be fine. The DataView is designed to process data from network devices, so the big-endian or small-endian order can be customized.

DataView(ArrayBuffer [, byte start position [, length]])

The DataView itself is also a constructor that takes an ArrayBuffer object as a parameter to generate the view.

let buffer = new ArrayBuffer(24);
let dv = new DataView(buffer);Copy the code

[4.2] DataView instance properties

DataView instances have the following properties, which have the same meaning as TypedArray instances of the same name.

DataView.prototype.buffer     // Returns the corresponding ArrayBuffer object
DataView.prototype.byteLength // Return the length of memory occupied in bytes
DataView.prototype.byteOffset // Returns which byte of the corresponding ArrayBuffer object the current view starts fromCopy the code

[4.3] Understand end sequences

Endianness, also called Endianness, indicates the arrangement of bytes in multiple bytes. The little endian is the least significant bit of a byte before the most significant bit (the big endian is the opposite). For example, the number 10, if represented in 16-bit binary, becomes 0000 0000 0000 1010, which in hexadecimal is 000A. Stored in the little endian, this value is represented as 0A00. Intel processors and most browsers use small-endian sequences, although big-endian sequences are more human reading habits. After this parameter is added, data in different storage modes can be processed flexibly.

[4.4] Read memory

  • GetInt8: Reads 1 byte and returns an 8-bit integer.
  • GetUint8: Reads 1 byte and returns an unsigned 8-bit integer.
  • GetInt16: Reads 2 bytes and returns a 16-bit integer.
  • GetUint16: Reads 2 bytes and returns an unsigned 16-bit integer.
  • GetInt32: Reads 4 bytes and returns a 32-bit integer.
  • GetUint32: Reads 4 bytes and returns an unsigned 32-bit integer.
  • GetFloat32: Reads 4 bytes and returns a 32-bit floating point number.
  • GetFloat64: Reads 8 bytes and returns a 64-bit floating point number.

Each of these get methods takes a byte ordinal (not a negative number, otherwise an error will be reported) indicating which byte to start reading from.

let buffer = new ArrayBuffer(24);
let dv = new DataView(buffer);

Read an 8-bit unsigned integer from the first byte
let v1 = dv.getUint8(0);

// Reads a 16-bit unsigned integer from the second byte
let v2 = dv.getUint16(1);

// Reads a 16-bit unsigned integer from the fourth byte
let v3 = dv.getUint16(3);

// The above code reads the first five bytes of an ArrayBuffer, which contains an 8-bit integer and two 16-bit integers.Copy the code

If two or more bytes are read at a time, it must be clear how the data is stored, whether in small-endian or big-endian order. By default, DataView’s GET method uses big-endian to read data. If small-endian reading is required, you must specify true as the second argument to the GET method.

// small endian byte order
let v1 = dv.getUint16(1.true);

// big endian byte order
let v2 = dv.getUint16(3.false);

// big endian byte order
let v3 = dv.getUint16(3);Copy the code

[4.5] Write into memory

  • SetInt8: Writes a 1-byte 8-bit integer.
  • SetUint8: Writes an 8-bit unsigned integer of 1 byte.
  • SetInt16: Writes a 16-bit integer of 2 bytes.
  • SetUint16: Writes a 2-byte 16-bit unsigned integer.
  • SetInt32: Writes a 32-bit integer of 4 bytes.
  • SetUint32: Writes a 4-byte 32-bit unsigned integer.
  • SetFloat32: Writes a 32-bit floating-point number of 4 bytes.
  • SetFloat64: Writes an 8-byte 64-bit floating-point number.

The set method takes two arguments. The first argument is the byte ordinal from which to start writing, and the second argument is the data being written. For methods that write two or more bytes, you need to specify a third argument, false or undefined to write in big-endian order and true to write in small-endian order.

// In the first byte, the 32-bit integer value 25 is written in big-endian order
dv.setInt32(0.25.false);

// In the fifth byte, the 32-bit integer value 25 is written in big-endian order
dv.setInt32(4.25);

In byte 9, a 32-bit floating-point number of 2.5 is written in small-endian order
dv.setFloat32(8.2.5.true);Copy the code

Five, specific application

【 5.1 】 webSocket

WebSocket can send or receive binary data through an arrayBuffer.

let socket = new WebSocket('ws: / / 127.0.0.1:8081');
socket.binaryType = 'arraybuffer';

socket.addEventListener('open'.function (event) {
  let typedArray = new Uint8Array(4);
  socket.send(typedArray.buffer);
});

socket.addEventListener('message'.function (event) {
  let arrayBuffer = event.data;
  / /...
});Copy the code

【 5.2 】 Ajax

Traditionally, the server can only return text data through AJAX operations, i.e. the responseType property defaults to text. XMLHttpRequest version 2 XHR2 allows the server to return binary data in two cases. If you know exactly what binary data type to return, you can set the responseType to arrayBuffer. If you don’t know, set it to BLOb.

let xhr = new XMLHttpRequest();
xhr.open('GET', someUrl);
xhr.responseType = 'arraybuffer';

xhr.onload = function () {
  let arrayBuffer = xhr.response;
  / /...
};

xhr.send();Copy the code

【 5.3 】 Canvas

Convert Canvas data to binary format

imagedata = context.getImageData(0.0, imagewidth,imageheight);    
let canvaspixelarray = imagedata.data;   
  
let canvaspixellen = canvaspixelarray.length;  
let bytearray = new Uint8Array(canvaspixellen);  
  
for (let i=0; i<canvaspixellen; ++i) { bytearray[i] = canvaspixelarray[i]; }Copy the code

The code for restoring binary data to an image is as follows. Note that we cannot retrieve the data directly from the arrayBuffer and put it on the Canvas

let bytearray = new Uint8Array(event.data);  
   
let tempcanvas = document.createElement('canvas');  
    tempcanvas.height = imageheight;  
    tempcanvas.width = imagewidth;  
let tempcontext = tempcanvas.getContext('2d');  
let imgdata = tempcontext.getImageData(0.0,imagewidth,imageheight);  
let imgdataimgdatalen = imgdata.data.length;  
   
for(let i=8; i<imgdatalen; i++) { imgdata.data[i] = bytearray[i]; } tempcontext.putImageData(imgdata,0.0);  Copy the code


The article is updated every week. You can search “Front-end highlights” on wechat to read it in the first time, and reply to [Books] to get 200G video materials and 30 PDF books