Arreglos tipados de JavaScript
Los arreglos tipados en JavaScript son objetos similares a arreglos que proporcionan un mecanismo para leer y escribir datos binarios sin procesar en búferes de memoria. Como ya sabrás, los objetos Arreglo
crecen y se encogen dinámicamente y pueden tener cualquier valor de JavaScript. Los motores de JavaScript realizan optimizaciones para que estos arreglos sean rápidos.
Sin embargo, a medida que las aplicaciones web se vuelven cada vez más poderosas, agregando características como manipulación de audio y video, acceso a datos sin procesar usando WebSockets
, etc., ha quedado claro que hay momentos en los que sería útil que el código JavaScript pudiera manipular rápida y fácilmente datos binarios sin procesar. Aquí es donde entran en juego los arreglos tipados. Cada entrada en un arreglo tipado de JavaScript es un valor binario sin procesar en uno de los formatos admitidos, desde números enteros de 8 bits hasta números de punto flotante de 64 bits.
Sin embargo, los arreglos tipados no se deben confundir con los arreglos normales, ya que llamar a Array.isArray()
en un arreglo tipado devuelve false
. Además, no todos los métodos disponibles para arreglos normales son compatibles con arreglos tipados (por ejemplo, push
y pop
).
Búferes y vistas: arquitectura de los arreglos tipados
Para lograr la máxima flexibilidad y eficiencia, los arreglos de JavaScript dividen la implementación en búferes y vistas. Un búfer (implementado por el objeto ArrayBuffer
es un objeto que representa una porción de datos; no tiene ningún formato del que hablar y no ofrece ningún mecanismo para acceder a su contenido. Para acceder a la memoria contenida en un búfer, necesitas usar una vista. Una vista proporciona un contexto — es decir, un tipo de dato, un desplazamiento inicial y el número de elementos — que convierte los datos en un arreglo tipado.
ArrayBuffer
ArrayBuffer
es un tipo de dato que se utiliza para representar un búfer de datos binarios genérico de longitud fija. No puedes manipular directamente el contenido de un ArrayBuffer
; en su lugar, crea una vista de arreglo tipado o un DataView
que representa el búfer en un formato específico, y lo usa para leer y escribir el contenido del búfer.
Vistas de arreglos tipados
Las vistas de arreglos tipados tienen nombres autodescriptivos y proporcionan vistas para todos los tipos numéricos habituales tal como Int8
, Uint32
, Float64
y así sucesivamente. Hay una vista de arreglo tipado especial, la Uint8ClampedArray
. Esta fija los valores entre 0 y 255. Tipos de datos JavaScript
Tipo | Intervalo de valores | Tamaño en bytes | Descripción | Tipo de IDL web | Tipo C equivalente |
---|---|---|---|---|---|
Int8Array |
-128 a 127 |
1 | Dos enteros complementarios de 8 bits con signo | byte |
int8_t |
Uint8Array |
0 a 255 |
1 | Entero de 8-bit sin signo | octet |
uint8_t |
Uint8ClampedArray |
0 a 255 |
1 | Entero de 8 bits sin signo (sujeto) | octet |
uint8_t |
Int16Array |
-32768 a 32767 |
2 | Dos enteros complementarios de 16 bits con signo | short |
int16_t |
Uint16Array |
0 a 65535 |
2 | Entero de 16 bits sin signo | Short sin signo |
uint16_t |
Int32Array |
-2147483648 a 2147483647 |
4 | dos enteros complementarios de 32 bits con signo | long |
int32_t |
Uint32Array |
0 a 4294967295 |
4 | Enteros de 32 bits sin signo | long sin signo |
uint32_t |
Float32Array |
1.2 ×10-38 a 3.4 ×1038 |
4 | Número de coma flotante IEEE de 32 bits (7 dígitos significativos, p. ej., 1.1234567 ) |
float sin restricciones |
float |
Float64Array |
1.2 ×10-38 a 3.4 ×10308 |
8 | Número de coma flotante IEEE de 64 bits (16 dígitos significativos, p. ej., 1.123...15 ) |
doble sin restricciones |
double |
BigInt64Array |
-263 a 263-1 |
8 | Dos enteros complementarios de 64 bits con signo | bigint |
int64_t (long long con signo) |
BigUint64Array |
0 a 264-1 |
8 | Entero de 64 bits sin signo | bigint |
uint64_t (long long sin signo) |
DataView
DataView
es una interfaz de bajo nivel que proporciona una API captadora (getter
)/(setter
) establecedora para leer y escribir datos arbitrarios en el búfer. Esto es útil cuando se trata de diferentes tipos de datos, por ejemplo. Las vistas de arreglos tipados están en el orden de bytes nativo (consulta Endianness de tu plataforma. Con un DataView
puedes controlar el orden de bytes. Es big-endian
de manera predeterminada y se puede establecer en little-endian
en los métodos captadores/establecedores.
APIs web que utilizan arreglos tipados
Estos son algunos ejemplos de APIs que utilizan arreglos tipados; hay otras, y todo el tiempo surgen más.
FileReader.prototype.readAsArrayBuffer()
-
El método
FileReader.prototype.readAsArrayBuffer()
comienza a leer el contenido del Blob o File. XMLHttpRequest.prototype.send()
-
El método
send()
de instancias deXMLHttpRequest
ahora admiten arreglos tipados y objetosArrayBuffer
como argumento. ImageData.data
-
Es un
Uint8ClampedArray
que representa un arreglo unidimensional que contiene los datos en el orden RGBA, con valores enteros entre0
y255
inclusive.
Ejemplos
Usar vistas con búferes
En primer lugar, necesitaremos crear un búfer, aquí con una longitud fija de 16 bytes:
let buffer = new ArrayBuffer(16);
En este punto, tenemos una porción de memoria cuyos bytes están todos preiniciados a 0. Sin embargo, no hay mucho que podamos hacer con él. Podemos confirmar que de hecho tiene 16 bytes de longitud, y eso es todo:
if (buffer.byteLength === 16) {
console.log("Sí, son 16 bytes");
} else {
console.log("¡Oh no, es del tamaño incorrecto!");
}
Antes de que podamos trabajar realmente con este búfer, necesitamos crear una vista. Creemos una vista que trate los datos en el búfer como un arreglo de enteros de 32 bits con signo:
let int32View = new Int32Array(buffer);
Ahora podemos acceder a los campos del arreglo como un arreglo normal:
for (let i = 0; i < int32View.length; i++) {
int32View[i] = i * 2;
}
Esto completa las 4 entradas en el arreglo (4 entradas de 4 bytes cada una suman 16 bytes en total) con los valores 0
, 2
, 4
y 6
.
Múltiples vistas sobre los mismos datos
Las cosas comienzan a ponerse realmente interesantes cuando consideras que puedes crear múltiples vistas sobre los mismos datos. Por ejemplo, dado el código anterior, podemos continuar así:
let int16View = new Int16Array(buffer);
for (let i = 0; i < int16View.length; i++) {
console.log("Entrada " + i + ": " + int16View[i]);
}
Aquí creamos una vista entera de 16 bits que comparte el mismo búfer que la vista existente de 32 bits y sacamos todos los valores en el búfer como enteros de 16 bits. Ahora obtenemos la salida 0
, 0
, 2
, 0
, 4
, 0
, 6
, 0
.
Sin embargo, puedes dar un paso más. Considera esto:
int16View[0] = 32;
console.log("La entrada 0 en el arreglo de 32 bits ahora es " + int32View[0]);
La salida de esto es "La entrada 0 en el arreglo de 32 bits ahora es 32"
.
En otras palabras, los dos arreglos se ven simplemente en el mismo búfer de datos, tratándolo como formatos diferentes. Lo puedes hacer con cualquier tipo de vista
.
Trabajar con complejas estructuras de datos
Al combinar un solo búfer con múltiples vistas de diferentes tipos, comenzando con diferentes desplazamientos en el búfer, puedes interactuar con objetos de datos que contienen múltiples tipos de datos. Esto te permite, por ejemplo, interactuar con complejas estructuras de datos WebGL, archivos de datos o estructuras C que necesitas utilizar mientras usas js-ctypes.
Considera esta estructura C:
struct someStruct {
unsigned long id;
char username[16];
float amountDue;
};
Puedes acceder a un búfer que contiene datos en un formato como este:
let buffer = new ArrayBuffer(24);
// ... lee los datos en el búfer ...
let idView = new Uint32Array(buffer, 0, 1);
let usernameView = new Uint8Array(buffer, 4, 16);
let amountDueView = new Float32Array(buffer, 20, 1);
Luego puedes acceder, por ejemplo, al monto adeudado con amountDueView[0]
.
Nota: La Data_structure_alignment en una estructura C depende de la plataforma. Toma precauciones y consideraciones para estas diferencias de relleno.
Conversión a arreglos normales
Después de procesar un arreglo con tipo, a veces es útil volver a convertirla en un arreglo normal para beneficiarse del prototipo Array
. Esto se puede hacer usando Array.from()
, o usando el siguiente código donde Array.from()
no es compatible.
let typedArray = new Uint8Array([1, 2, 3, 4]),
normalArray = Array.prototype.slice.call(typedArray);
normalArray.length === 4;
normalArray.constructor === Array;