Типизированные массивы — двоичные данные в браузере

Ilmari Heikkinen

Введение

Типизированные массивы — относительно недавнее дополнение к браузерам, возникшее из-за необходимости иметь эффективный способ обработки двоичных данных в WebGL. Типизированный массив — это участок памяти с типизированным представлением в нем, во многом похожий на то, как массивы работают в C. Поскольку типизированный массив поддерживается необработанной памятью, механизм JavaScript может передавать память непосредственно в собственные библиотеки без необходимости кропотливого преобразования данные в собственное представление. В результате типизированные массивы работают намного лучше, чем массивы JavaScript, при передаче данных в WebGL и другие API, работающие с двоичными данными.

Представления типизированных массивов действуют как массивы одного типа для сегмента ArrayBuffer. Существуют представления для всех обычных числовых типов с понятными именами, такими как Float32Array, Float64Array, Int32Array и Uint8Array. Существует также специальное представление, которое заменило тип массива пикселей в ImageData Canvas: Uint8ClampedArray.

DataView — это второй тип представления, предназначенный для обработки разнородных данных. Вместо API-интерфейса, подобного массиву, объект DataView предоставляет вам API-интерфейс get/set для чтения и записи произвольных типов данных с произвольными смещениями байтов. DataView отлично подходит для чтения и записи заголовков файлов и других подобных структурных данных.

Основы использования типизированных массивов

Представления типизированных массивов

Чтобы использовать типизированные массивы, вам необходимо создать ArrayBuffer и представление для него. Самый простой способ — создать представление типизированного массива нужного размера и типа.

// Typed array views work pretty much like normal arrays.
var f64a = new Float64Array(8);
f64a[0] = 10;
f64a[1] = 20;
f64a[2] = f64a[0] + f64a[1];

Существует несколько различных типов представлений типизированных массивов. Все они используют один и тот же API, поэтому, как только вы научитесь использовать один из них, вы в значительной степени поймете, как использовать их все. В следующем примере я собираюсь создать по одному из каждого существующего в настоящее время представления типизированного массива.

// Floating point arrays.
var f64 = new Float64Array(8);
var f32 = new Float32Array(16);

// Signed integer arrays.
var i32 = new Int32Array(16);
var i16 = new Int16Array(32);
var i8 = new Int8Array(64);

// Unsigned integer arrays.
var u32 = new Uint32Array(16);
var u16 = new Uint16Array(32);
var u8 = new Uint8Array(64);
var pixels = new Uint8ClampedArray(64);

Последний вариант немного особенный: он ограничивает входные значения от 0 до 255. Это особенно удобно для алгоритмов обработки изображений Canvas, поскольку теперь вам не нужно вручную ограничивать математические вычисления обработки изображений, чтобы избежать переполнения 8-битного диапазона.

Например, вот как можно применить гамма-фактор к изображению, хранящемуся в Uint8Array. Не очень красиво:

u8[i] = Math.min(255, Math.max(0, u8[i] * gamma));

С помощью Uint8ClampedArray вы можете пропустить ручное зажимание:

pixels[i] *= gamma;

Другой способ создания представлений типизированного массива — сначала создать ArrayBuffer, а затем создать представления, указывающие на него. API-интерфейсы, которые предоставляют вам внешние данные, обычно работают с ArrayBuffers, поэтому именно так вы получаете к ним представление типизированного массива.

var ab = new ArrayBuffer(256); // 256-byte ArrayBuffer.
var faFull = new Uint8Array(ab);
var faFirstHalf = new Uint8Array(ab, 0, 128);
var faThirdQuarter = new Uint8Array(ab, 128, 64);
var faRest = new Uint8Array(ab, 192);

Вы также можете иметь несколько представлений для одного и того же ArrayBuffer.

var fa = new Float32Array(64);
var ba = new Uint8Array(fa.buffer, 0, Float32Array.BYTES_PER_ELEMENT); // First float of fa.

Чтобы скопировать типизированный массив в другой типизированный массив, самый быстрый способ — использовать метод set типизированного массива. Для использования по принципу memcpy создайте Uint8Arrays в буферах представлений и используйте set для копирования данных.

function memcpy(dst, dstOffset, src, srcOffset, length) {
  var dstU8 = new Uint8Array(dst, dstOffset, length);
  var srcU8 = new Uint8Array(src, srcOffset, length);
  dstU8.set(srcU8);
};

Просмотр данных

Чтобы использовать ArrayBuffers, содержащие данные разнородных типов, самый простой способ — использовать DataView для буфера. Предположим, у нас есть формат файла, который имеет заголовок с 8-битным беззнаковым целым числом, за которым следуют два 16-битных целых числа, за которыми следует массив полезных данных из 32-битных чисел с плавающей запятой. Прочитать это обратно с представлениями типизированного массива можно, но это немного затруднительно. С помощью DataView мы можем прочитать заголовок и использовать представление типизированного массива для массива с плавающей запятой.

var dv = new DataView(buffer);
var vector_length = dv.getUint8(0);
var width = dv.getUint16(1); // 0+uint8 = 1 bytes offset
var height = dv.getUint16(3); // 0+uint8+uint16 = 3 bytes offset
var vectors = new Float32Array(width*height*vector_length);
for (var i=0, off=5; i<vectors.length; i++, off+=4) {
  vectors[i] = dv.getFloat32(off);
}

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

...
var width = dv.getUint16(1, true);
var height = dv.getUint16(3, true);
...
vectors[i] = dv.getFloat32(off, true);
...

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

DataView также имеет методы для записи значений в буферы. Эти сеттеры называются так же, как и геттеры: «set», за которым следует тип данных.

dv.setInt32(0, 25, false); // set big-endian int32 at byte offset 0 to 25
dv.setInt32(4, 25); // set big-endian int32 at byte offset 4 to 25
dv.setFloat32(8, 2.5, true); // set little-endian float32 at byte offset 8 to 2.5

Обсуждение порядка байтов

Порядок байтов, или порядок байтов, — это порядок, в котором многобайтовые числа хранятся в памяти компьютера. Термин big-endian описывает архитектуру ЦП, в которой первым сохраняется наиболее значимый байт; Little-endian — первый младший байт. Какой порядок байтов используется в конкретной архитектуре ЦП, совершенно произвольно; есть веские причины выбрать любой из них. Фактически, некоторые процессоры можно настроить для поддержки данных как с прямым, так и с прямым порядком байтов.

Почему вам нужно беспокоиться о порядке байтов? Причина проста. При чтении или записи данных с диска или сети необходимо указывать порядок байтов данных. Это гарантирует правильную интерпретацию данных независимо от порядка байтов процессора, который с ними работает. В нашем все более сетевом мире крайне важно правильно поддерживать все виды устройств, как с прямым, так и с прямым порядком байтов, которым может потребоваться работа с двоичными данными, поступающими с серверов или других узлов в сети.

Интерфейс DataView специально разработан для чтения и записи данных в файлы и сеть, а также из них. DataView оперирует данными с указанным порядком байтов . Порядок байтов, большой или маленький, необходимо указывать при каждом доступе к каждому значению, гарантируя, что вы получите последовательные и правильные результаты при чтении или записи двоичных данных, независимо от порядка байтов ЦП, на котором работает браузер.

Обычно, когда ваше приложение считывает двоичные данные с сервера, вам необходимо один раз просмотреть их, чтобы преобразовать в структуры данных, которые ваше приложение использует внутри себя. На этом этапе следует использовать DataView. Не рекомендуется использовать представления многобайтовых типизированных массивов (Int16Array, Uint16Array и т. д.) непосредственно с данными, полученными через XMLHttpRequest, FileReader или любой другой API ввода-вывода, поскольку представления типизированных массивов используют собственный порядок байтов ЦП. Подробнее об этом позже.

Давайте рассмотрим пару простых примеров. Формат файлов Windows BMP был стандартным форматом для хранения изображений на заре Windows. В документации, указанной выше, четко указано, что все целочисленные значения в файле хранятся в формате с прямым порядком байтов. Вот фрагмент кода, который анализирует начало заголовка BMP с использованием библиотеки DataStream.js , прилагаемой к этой статье:

function parseBMP(arrayBuffer) {
  var stream = new DataStream(arrayBuffer, 0,
    DataStream.LITTLE_ENDIAN);
  var header = stream.readUint8Array(2);
  var fileSize = stream.readUint32();
  // Skip the next two 16-bit integers
  stream.readUint16();
  stream.readUint16();
  var pixelOffset = stream.readUint32();
  // Now parse the DIB header
  var dibHeaderSize = stream.readUint32();
  var imageWidth = stream.readInt32();
  var imageHeight = stream.readInt32();
  // ...
}

Вот еще один пример, на этот раз из демонстрации рендеринга High Dynamic Range в проекте примеров WebGL . В этой демонстрации загружаются необработанные данные с плавающей запятой с прямым порядком байтов, представляющие текстуры с широким динамическим диапазоном, и их необходимо загрузить в WebGL. Вот фрагмент кода, который правильно интерпретирует значения с плавающей запятой на всех архитектурах ЦП. Предположим, что переменная «arrayBuffer» — это ArrayBuffer, который только что был загружен с сервера через XMLHttpRequest:

var arrayBuffer = ...;
var data = new DataView(arrayBuffer);
var tempArray = new Float32Array(
  data.byteLength / Float32Array.BYTES_PER_ELEMENT);
var len = tempArray.length;
// Incoming data is raw floating point values
// with little-endian byte ordering.
for (var jj = 0; jj < len; ++jj) {
  tempArray[jj] =
    data.getFloat32(jj * Float32Array.BYTES_PER_ELEMENT, true);
}
gl.texImage2D(...other arguments...,
  gl.RGB, gl.FLOAT, tempArray);

Эмпирическое правило таково: получив двоичные данные с веб-сервера, выполните один проход по ним с помощью DataView. Считайте отдельные числовые значения и сохраните их в какой-либо другой структуре данных: либо в объекте JavaScript (для небольших объемов структурированных данных), либо в представлении типизированного массива (для больших блоков данных). Это гарантирует корректную работу вашего кода на всех типах процессоров. Также используйте DataView для записи данных в файл или сеть и обязательно укажите аргумент LittleEndian для различных методов набора , чтобы создать формат файла, который вы создаете или используете.

Помните, что все данные, которые передаются по сети, неявно имеют формат и порядок байтов (по крайней мере, для любых многобайтовых значений). Обязательно четко определите и задокументируйте формат всех данных, которые ваше приложение отправляет по сети.

API браузера, использующие типизированные массивы

Я собираюсь дать вам краткий обзор различных API браузеров, которые в настоящее время используют типизированные массивы. Текущая версия включает WebGL, Canvas, API веб-аудио, XMLHttpRequests, WebSockets, веб-работники, API источников мультимедиа и API файлов. Из списка API видно, что типизированные массивы хорошо подходят для работы с мультимедиа, требующей производительности, а также для эффективной передачи данных.

ВебГЛ

Первое использование типизированных массивов было в WebGL, где они использовались для передачи данных буфера и данных изображения. Чтобы установить содержимое объекта буфера WebGL, вы используете вызов gl.bufferData() с типизированным массивом.

var floatArray = new Float32Array([1,2,3,4,5,6,7,8]);
gl.bufferData(gl.ARRAY_BUFFER, floatArray);

Типизированные массивы также используются для передачи данных текстуры. Вот базовый пример передачи содержимого текстуры с использованием типизированного массива.

var pixels = new Uint8Array(16*16*4); // 16x16 RGBA image
gl.texImage2D(
  gl.TEXTURE_2D, // target
  0, // mip level
  gl.RGBA, // internal format
  16, 16, // width and height
  0, // border
  gl.RGBA, //format
  gl.UNSIGNED_BYTE, // type
  pixels // texture data
);

Вам также нужны типизированные массивы для чтения пикселей из контекста WebGL.

var pixels = new Uint8Array(320*240*4); // 320x240 RGBA image
gl.readPixels(0, 0, 320, 240, gl.RGBA, gl.UNSIGNED_BYTE, pixels);

Холст 2D

Недавно объект Canvas ImageData был адаптирован для работы со спецификацией типизированных массивов. Теперь вы можете получить представление пикселей на элементе холста в виде типизированных массивов. Это полезно, поскольку теперь вы также можете создавать и редактировать массивы пикселей холста без необходимости возиться с элементом холста.

var imageData = ctx.getImageData(0,0, 200, 100);
var typedArray = imageData.data // data is a Uint8ClampedArray

XMLHttpRequest2

XMLHttpRequest получил улучшение типизированного массива, и теперь вы можете получать ответ типизированного массива вместо необходимости анализировать строку JavaScript в типизированный массив. Это действительно удобно для передачи полученных данных непосредственно в мультимедийные API и для анализа двоичных файлов, полученных из сети.

Все, что вам нужно сделать, это установить для параметра responseType объекта XMLHttpRequest значение «arraybuffer».

xhr.responseType = 'arraybuffer';

Напомним, что вы должны учитывать проблемы с порядком байтов при загрузке данных из сети! См. раздел о порядке байтов выше.

Файловые API

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

reader.readAsArrayBuffer(file);

Здесь также следует помнить о порядке байтов. Подробности смотрите в разделе о порядке байтов.

Передаваемые объекты

Передаваемые объекты в postMessage значительно ускоряют передачу двоичных данных в другие окна и веб-работники. Когда вы отправляете объект Worker как Transferable, объект становится недоступным в отправляющем потоке, и принимающий Worker получает право собственности на объект. Это обеспечивает высокооптимизированную реализацию, при которой отправленные данные не копируются, а получателю передается только право владения типизированным массивом.

Чтобы использовать передаваемые объекты с веб-воркерами, вам необходимо использовать метод webkitPostMessage для работника. Метод webkitPostMessage работает так же, как postMessage, но принимает два аргумента вместо одного. Добавленный второй аргумент представляет собой массив объектов, которые вы хотите передать рабочему процессу.

worker.webkitPostMessage(oneGBTypedArray, [oneGBTypedArray]);

Чтобы получить объекты обратно от работника, он может передать их обратно в основной поток таким же образом.

webkitPostMessage({results: grand, youCanHaveThisBack: oneGBTypedArray}, [oneGBTypedArray]);

Ноль копий, ух!

API источника мультимедиа

Недавно медиа-элементы также получили некоторые преимущества Typed Array в виде Media Source API . Вы можете напрямую передать типизированный массив, содержащий видеоданные, в элемент видео, используя webkitSourceAppend. Это заставляет элемент video добавлять видеоданные после существующего видео. SourceAppend отлично подходит для создания межстраничных объявлений, плейлистов, потоковой передачи и других целей, когда вам может потребоваться воспроизвести несколько видео, используя один видеоэлемент.

video.webkitSourceAppend(uint8Array);

Двоичные веб-сокеты

Вы также можете использовать типизированные массивы с WebSockets, чтобы избежать необходимости преобразования всех ваших данных в строку. Отлично подходит для написания эффективных протоколов и минимизации сетевого трафика.

socket.binaryType = 'arraybuffer';

Фу! На этом обзор API завершен. Давайте перейдем к рассмотрению сторонних библиотек для обработки типизированных массивов.

Сторонние библиотеки

jDataView

jDataView реализует прокладку DataView для всех браузеров. Раньше DataView была функцией только WebKit, но теперь она поддерживается большинством других браузеров. Команда разработчиков Mozilla находится в процессе установки патча для включения DataView и в Firefox.

Эрик Бидельман из группы по связям с разработчиками Chrome написал небольшой пример чтения тегов MP3 ID3 , использующий jDataView. Вот пример использования из сообщения в блоге:

var dv = new jDataView(arraybuffer);

// "TAG" starts at byte -128 from EOF.
// See http://en.wikipedia.org/wiki/ID3
if (dv.getString(3, dv.byteLength - 128) == 'TAG') {
  var title = dv.getString(30, dv.tell());
  var artist = dv.getString(30, dv.tell());
  var album = dv.getString(30, dv.tell());
  var year = dv.getString(4, dv.tell());
} else {
  // no ID3v1 data found.
}

строковое кодирование

Работа со строками в типизированных массивах сейчас немного утомительна, но в этом помогает библиотека кодирования строк . Stringencoding реализует предложенную спецификацию кодирования строк Typed Array , поэтому это также хороший способ получить представление о том, что будет дальше.

Вот базовый пример использования строкового кодирования:

var uint8array = new TextEncoder(encoding).encode(string);
var string = new TextDecoder(encoding).decode(uint8array);

BitView.js

Я написал небольшую библиотеку манипуляций с типизированными массивами под названием BitView.js. Как следует из названия, он работает во многом аналогично DataView, за исключением того, что работает с битами. С помощью BitView вы можете получить и установить значение бита с заданным смещением бита в ArrayBuffer. BitView также имеет методы для хранения и загрузки 6-битных и 12-битных целых чисел с произвольными битовыми смещениями.

12-битные целые числа хороши для работы с экранными координатами, поскольку дисплеи обычно имеют размер менее 4096 пикселей по длинному измерению. Используя 12-битные целые числа вместо 32-битных, вы получаете уменьшение размера на 62%. В качестве более радикального примера я работал с шейп-файлами, которые используют 64-битные числа с плавающей запятой для координат, но мне не нужна была точность, потому что модель должна была отображаться только в размере экрана. Переход на 12-битные базовые координаты с 6-битными дельтами для кодирования изменений по сравнению с предыдущей координатой позволил уменьшить размер файла до десятой части. Вы можете увидеть демо-версию здесь .

Вот пример использования BitView.js:

var bv = new BitView(arrayBuffer);
bv.setBit(4, 1); // Set fourth bit of arrayBuffer to 1.
bv.getBit(17); // Get 17th bit of arrayBuffer.

bv.getBit(50*8 + 3); // Get third bit of 50th byte in arrayBuffer.

bv.setInt6(3, 18); // Write 18 as a 6-bit int to bit position 3 in arrayBuffer.
bv.getInt12(9); // Read a 12-bit int from bit position 9 in arrayBuffer.

DataStream.js

Одна из самых интересных особенностей типизированных массивов — то, как они упрощают работу с двоичными файлами в JavaScript. Вместо того, чтобы анализировать строку посимвольно и вручную преобразовывать символы в двоичные числа и тому подобное, теперь вы можете получить ArrayBuffer с помощью XMLHttpRequest и напрямую обработать его с помощью DataView. Это позволяет легко, например, загрузить файл MP3 и прочитать теги метаданных для использования в вашем аудиоплеере. Или загрузите шейп-файл и превратите его в модель WebGL. Или прочитайте теги EXIF ​​из JPEG и покажите их в своем приложении для слайд-шоу.

Проблема с ArrayBuffer XHR заключается в том, что чтение структурных данных из буфера представляет собой некоторую боль. DataView хорош для чтения нескольких чисел за раз с соблюдением порядка байтов, представления типизированных массивов хороши для чтения массивов чисел с собственным порядком байтов, выровненных по размеру элемента. Чего нам не хватало, так это способа чтения массивов и структур данных удобным способом с соблюдением порядка байтов. Введите DataStream.js.

DataStream.js — это библиотека типизированных массивов, которая считывает и записывает скаляры, строки, массивы и структуры данных из ArrayBuffers в файловом режиме.

Пример чтения массива чисел с плавающей запятой из ArrayBuffer:

// without DataStream.js
var dv = new DataView(buffer);
var f32 = new Float32Array(buffer.byteLength / 4);
var littleEndian = true;
for (var i = 0; i<f32.length; i++) {
  f32[i] = dv.getFloat32(i*4, littleEndian);
}

// with DataStream.js
var ds = new DataStream(buffer);
ds.endianness = DataStream.LITTLE_ENDIAN;
var f32 = ds.readFloat32Array(ds.byteLength / 4);

DataStream.js действительно полезен при чтении более сложных данных. Предположим, у вас есть метод, считывающий маркеры JPEG:

// without DataStream.js
var dv = new DataView(buffer);
var objs = [];
for (var i=0; i<buffer.byteLength;) {
  var obj = {};
  obj.tag = dv.getUint16(i);
  i += 2;
  obj.length = dv.getUint16(i);
  i += 2;
  obj.data = new Uint8Array(obj.length - 2);
  for (var j=0; j<obj.data.length; j++,i++) {
    obj.data[j] = dv.getUint8(i);
  }
  objs.push(obj);
}

// with DataStream.js
var ds = new DataStream(buffer);
ds.endianness = ds.BIG_ENDIAN;
var objs = [];
while (!ds.isEof()) {
  var obj = {};
  obj.tag = ds.readUint16();
  obj.length = ds.readUint16();
  obj.data = ds.readUint8Array(obj.length - 2);
  objs.push(obj);
}

Или используйте метод DataStream.readStruct для чтения структур данных. Метод readStruct принимает массив определений структуры, содержащий типы членов структуры. Он имеет функции обратного вызова для обработки сложных типов, а также обрабатывает массивы данных и вложенные структуры:

// with DataStream.readStruct
ds.readStruct([
  'objs', ['[]', [ // objs: array of tag,length,data structs
    'tag', 'uint16',
    'length', 'uint16',
    'data', ['[]', 'uint8', function(s,ds){ return s.length - 2; }], // get length with a function
  '*'] // read in as many struct as there are
]);

Как видите, определение структуры представляет собой плоский массив пар [имя, тип]. Вложенные структуры создаются с помощью массива для типа. Массивы определяются с использованием массива из трех элементов, где второй элемент — это тип элемента массива, а третий элемент — это длина массива (либо как число, либо как ссылка на ранее прочитанное поле, либо как функция обратного вызова). Первый элемент определения массива не используется.

Возможные значения типа следующие:

Number types

Unsuffixed number types use DataStream endianness.
To explicitly specify endianness, suffix the type with
'le' for little-endian or 'be' for big-endian,
e.g. 'int32be' for big-endian int32.

  'uint8' -- 8-bit unsigned int
  'uint16' -- 16-bit unsigned int
  'uint32' -- 32-bit unsigned int
  'int8' -- 8-bit int
  'int16' -- 16-bit int
  'int32' -- 32-bit int
  'float32' -- 32-bit float
  'float64' -- 64-bit float

String types

  'cstring' -- ASCII string terminated by a zero byte.
  'string:N' -- ASCII string of length N.
  'string,CHARSET:N' -- String of byteLength N encoded with given CHARSET.
  'u16string:N' -- UCS-2 string of length N in DataStream endianness.
  'u16stringle:N' -- UCS-2 string of length N in little-endian.
  'u16stringbe:N' -- UCS-2 string of length N in big-endian.

Complex types

  [name, type, name_2, type_2, ..., name_N, type_N] -- Struct

  function(dataStream, struct) {} -- Callback function to read and return data.

  {get: function(dataStream, struct) {}, set: function(dataStream, struct) {}}
  -- Getter/setter functions to reading and writing data. Handy for using the
     same struct definition for both reading and writing.

  ['', type, length] -- Array of given type and length. The length can be either
                        a number, a string that references a previously-read
                        field, or a callback function(struct, dataStream, type){}.
                        If length is set to '*', elements are read from the
                        DataStream until a read fails.

Посмотреть живой пример чтения метаданных в формате JPEG можно здесь . В демонстрации используется DataStream.js для чтения структуры уровня тегов файла JPEG (наряду с некоторым анализом EXIF) и jpg.js для декодирования и отображения изображения JPEG в JavaScript.

История типизированных массивов

Типизированные массивы появились на ранней стадии реализации WebGL, когда мы обнаружили, что передача массивов JavaScript графическому драйверу вызывает проблемы с производительностью. В случае с массивами JavaScript привязка WebGL должна была выделить собственный массив и заполнить его, пройдя по массиву JavaScript и приведя каждый объект JavaScript в массиве к требуемому собственному типу.

Чтобы устранить проблему преобразования данных, Владимир Вукицевич из Mozilla написал CanvasFloatArray: массив с плавающей запятой в стиле C с интерфейсом JavaScript. Теперь вы можете редактировать CanvasFloatArray в JavaScript и передавать его непосредственно в WebGL без необходимости выполнять какую-либо дополнительную работу при привязке. В дальнейших итерациях CanvasFloatArray был переименован в WebGLFloatArray, который затем был переименован в Float32Array и разделен на резервный ArrayBuffer и типизированное представление Float32Array для доступа к буферу. Также были добавлены типы для других размеров целых чисел и с плавающей запятой, а также вариантов со знаком и без знака.

Рекомендации по проектированию

С самого начала разработка типизированных массивов была обусловлена ​​необходимостью эффективной передачи двоичных данных в собственные библиотеки. По этой причине представления типизированного массива работают с выровненными данными с собственным порядком байтов центрального процессора. Эти решения позволяют JavaScript достичь максимальной производительности во время таких операций, как отправка данных вершин на видеокарту.

DataView специально разработан для файлового и сетевого ввода-вывода, где данные всегда имеют указанный порядок байтов и могут не быть выровнены для максимальной производительности.

Разделение конструкции между сборкой данных в памяти (с использованием представлений типизированных массивов) и вводом-выводом (с использованием DataView) было осознанным. Современные движки JavaScript сильно оптимизируют представления типизированных массивов и достигают высокой производительности при числовых операциях с ними. Текущий уровень производительности представлений типизированных массивов стал возможен благодаря этому проектному решению.

Ссылки