Типизованные массивы JavaScript

В процессе перевода.

Типизованные массивы в JavaScript являются массиво-подобными объектами, предоставляющими механизм доступа к сырым двоичным данным. Как вы уже можете знать, массив Array растет и обрезается динамически, и может содержать элементы любого типа JavaScript. JavaScript благодаря оптимизациям JavaScript движков, массивы остаются быстрыми. Однако, со временем веб-приложения становятся все более и более мощными, появляется необходимость работы с аудио- и видео-данными, требуется доступ к сырым данным WebSocket`ов, и так далее. Становится очевидным, что возможность быстрой и эффективной работы с двоичными данными в JavaScript будет очень полезной. Для чего типизованные массивы и предназначены.

Не следует, однако, путать типизованные массивы с массивами обычными, так например вызов Array.isArray() к типизованному массиву вернет false. Более того, не все методы, доступные для обычных массивов поддерживаются типизованными массивами (взять хотя бы push и pop).

Буферы и представления: архитектура типизованных массивов

Для достижения максимальной гибкости и производительности, реализация типизованных массивов в JavaScript разделена  на буферы и представления. Буфер (ArrayBuffer) это объект, представляющий из себя набор данных. Он не имеет формата и не предоставляет возможности доступа к своему содержимому. Для доступа к памяти буфера вам нужно использовать представления. Представление являет собой контекст, имеющий тип данных, начальную позицию в буфере, и количество элементов — это позволяет представить данные в виде актуального типизованного массива.

Typed arrays in an ArrayBuffer

ArrayBuffer

Объект ArrayBuffer это стандартный набор бинарных данных с фиксированной длинной. Вы не можете манипулировать содержимым ArrayBuffer напрямую. Вместо этого, необходимо создать типизованное представление DataView, которое будет отображать буфер в определенном формате, и даст доступ на запись и чтение его содержимого.

Типизованные представления

Название типизованного представления массива говорит само за себя. Оно представляет массив в распространенных числовых форматах, таких как  Int8Uint32Float64 и так далее. Среди прочих, существует специальное представление Uint8ClampedArray. Оно ограничивает значения интервалом от 0 до 255. Это полезно, например, при Обработке данных изображения в Canvas.

DataView

Объект DataView это низкоуровневый интерфейс предоставляющий API для записи/чтения произвольных данных в буфер. Это полезно при работе с разнородными данными, например. В то время как типизованные представления всегда имеют порядок байт (смотрите Endianness) родной для вашей операционной системы, DataView позволяет контроллировать порядок байт (byte-order). По умолчанию это big-endian, но через АПИ можно установить little-endian.

Веб АПИ, использующие типизованные массивы

FileReader.prototype.readAsArrayBuffer()
Метод FileReader.prototype.readAsArrayBuffer() читает содержимое заданного Blob или File.
XMLHttpRequest.prototype.send()
Метод send() экземпляра XMLHttpRequest теперь в качестве аргумента поддерживает ArrayBuffer.
ImageData.data
Имеет тип Uint8ClampedArray и представляет изображение в виде одномерного массива, где цветовые компоненты расположены в порядке RGBA, и их значения принудительно ограниченны диапазоном от 0 до 255.

Примеры

Использование представлений с буферами

Прежде всего необходимо создать буфер. Здесь он, с фиксированной длинной 16 байт:

var buffer = new ArrayBuffer(16);

На данном этапе мы имеем область памяти в 16 байт инициализированной нулевыми значениями. Все что мы можем сделать сейчас, это убедиться что длинна буфера действительно 16 байт:

if (buffer.byteLength === 16) {
  console.log("Да, это 16 чудесных байтов.");
} else {
  console.log("О нет, размер не наш!");
} 

Прежде чем мы сможем приступить к полноценной работе с памятью, нам нужно создать представление. Давайте создадим представление, которое отображает буфер как массив из 32-битных целочисленных значений со знаком:

var int32View = new Int32Array(buffer);

Теперь мы можем получить доступ к элементам представления как к элементам обычного массива:

for (var i = 0; i < int32View.length; i++) {
  int32View[i] = i * 2;
}

Этот код поместит 4 элемента в буфер (4 элемента по 4 байта в каждом, дает 16 байт) равных 0, 2, 4, и 6.

Множество представлений для одних и тех же данных

Всё становится намного интереснее, если создать несколько разных представлений для одного и того же буфера. Например, приведенный выше код можно дополнить следующим образом:

var int16View = new Int16Array(buffer);

for (var i = 0; i < int16View.length; i++) {
  console.log('Entry ' + i + ': ' + int16View[i]);
}

Здесь мы создаем 16-битное целочисленное представление, которое ссылается на тот же самый буфер, что и 32-битное представление, и затем выводим все 16-битные элементы этого представления. Мы получим следующий вывод: 0, 0, 2, 0, 4, 0, 6, 0.

Можно пойти дальше. Оцените этот код:

int16View[0] = 32;
console.log('Элемент 0 в 32-битном представлении теперь равен ' + int32View[0]);

Результатом выолнения станет текст: "Элемент 0 в 32-битном представлении теперь равен 32". Другими словами, два массива на самом деле являются лишь разными представлениями одного и того же буфера данных в разных форматах. Вы можете повторить это с представлениями любого типа.

Работа со сложными структурами данных

Комбинируя буфер и множество представлений разного формата, имеющих разные смещения относильно начала буфера, можно управляться с объектами содержащими разнородные данные. Это позволяет, к примеру, взаимодействовать со сложными структурам из WebGL, файлами данных, или структурами языка C (сопоставление данных JS и C).

Рассмотрим следующую структуру из языка C:

struct someStruct {
  unsigned long id;
  char username[16];
  float amountDue;
};

Получить доступ к полям этой структуры можно следующим образом:

var buffer = new ArrayBuffer(24);

// ... поместить данные структуры в буфер ...

var idView = new Uint32Array(buffer, 0, 1);
var usernameView = new Uint8Array(buffer, 4, 16);
var amountDueView = new Float32Array(buffer, 20, 1);

Теперь получить или изменить значение поля amountDue, к примеру, можно путем обращения к amountDueView[0].

Примечание: Выравнивание данных в языке C является платформозависимым. Принимайте меры по вычислению правильных отступов в данных с учетом выравнивания.

Преобразование в обычные массивы

Иногда после обработки типизованного массива бывает полезно конвертировать его в обычный массив, чтобы получить доступ к методам прототипа Array. Для этих целей существует метод Array.from. А в тех случаях, когда Array.from не поддерживается, используйте следующий код:

var typedArray = new Uint8Array([1, 2, 3, 4]),
    normalArray = Array.prototype.slice.call(typedArray);
normalArray.length === 4;
normalArray.constructor === Array;

Спецификации

Спецификация Статус Коментарий
Typed Array Specification Устаревшая Вытеснено стандартом ECMAScript 2015.
ECMAScript 2015 (6th Edition, ECMA-262)
Определение 'TypedArray Objects' в этой спецификации.
Стандарт Начальное определение в стандарте ECMA.
ECMAScript Latest Draft (ECMA-262)
Определение 'TypedArray Objects' в этой спецификации.
Черновик  

Совместимость с браузерами

Возможность Chrome Firefox (Gecko) Internet Explorer Opera Safari
Базовая поддержка 7.0 4.0 (2) 10 11.6 5.1
Возможность Android Chrome for Android Firefox Mobile (Gecko) IE Mobile Opera Mobile Safari Mobile
Базовая поддержка 4.0 (Да) 4.0 (2) 10 11.6 4.2

Смотрите также

Метки документа и участники

 Внесли вклад в эту страницу: Anomeon, some_vlad, Megabyteceer
 Обновлялась последний раз: Anomeon,