定型数组(typed array)是一种类似数组的对象,提供了一种用于在内存缓冲区中访问原始二进制数据的机制,可以提升向原生库传输数据的效率。

历史由来

随着浏览器的流行,人们期待通过它来运行复杂的 3D 应用程序。

早在 2006 年,Mozilla、Opera 等浏览器提供商就实验性地在浏览器中增加了用于渲染复杂图形应用程序的编程平台。其目标是开发一套 JavaScript API,从而充分利用 3D 图形 API 和 GPU 加速,以便在 <canvas> 元素上渲染复杂的图形。

最后得到的 JavaScript API 是基于 OpenGL ES(OpenGL for Embedded Systems)2.0 规范的。OpenGL ES 是 OpenGL 专注于 2D 和 3D 计算机图形的子集。这个新 API 被命名为 WebGL(Web Graphics Library),于 2011年 发布 1.0 版。有了它,开发者能够编写涉及复杂图形的应用程序,它会被兼容 WebGL 的浏览器原生解释执行。

在 WebGL 的早期版本中,因为 JavaScript 数组与原生数组之间不匹配,出现了性能问题。图形驱动程序 API 通常不需要以 JavaScript 默认双精度浮点格式传递给它们的数值 。

因此,每次 WebGL 与 JavaScript 之间传递数组时,WebGL 绑定都需要在目标环境分配新数组,以其当前格式迭代数组,然后将数值转型为新数组中的适当格式,而这些要花费很多时间。

这当然是难以接受的,Mozilla 为解决这个问题实现了 CanvasFloatArray。JavaScript 运行时使用这个类型可以分配、读取和写入数组。这个数组可以直接传给底层图形驱动程序 API,也可以直接从底层获取到。最终,CanvasFloatArray 变成了 Float32Array。

ArrayBuffer 缓冲

ArrayBuffer 是所有定型数组及视图引用的基本单位。

ArrayBuffer() 构造函数可用于 在内存中分配特定数量的字节空间,分配时会将所有二进制位初始化为 0 。

const buf = new ArrayBuffer(16); // 在内存中分配 16 字节
console.log(buf.byteLength); // 16

ArrayBuffer 分配的内存不能超过 Number.MAX_SAFE_INTEGER(253-1) 字节,在分配失败时会抛出错误。

ArrayBuffer 一经创建就不能再调整大小,不过,可以使用 slice() 复制到一个新实例中。

const buf1 = new ArrayBuffer(16);
const buf2 = buf1.slice(4, 12);
console.log(buf2.byteLength);  // 8

要读取或写入ArrayBuffer,必须通过视图,视图引用的是 ArrayBuffer 中存储的二进制数据。

DataView 视图

DataView 视图专为文件 I/O 和网络 I/O 设计,其 API 支持对缓冲数据的高度控制,但相比其他类型的视图,性能差一些。DataView 对缓冲内容没有任何预设,也不能迭代。

创建 DataView

必须在对已有的 ArrayBuffer 实例读取或写入时才能创建 DataView 实例。DataView 实例可以使用全部或部分 ArrayBuffer,且维护着对缓冲实例的引用,以及视图在缓冲中开始的位置。

const buf = new ArrayBuffer(16);// 使用整个 ArrayBuffer
const fullDataView = new DataView(buf);
console.log(fullDataView.byteOffset); // 0
console.log(fullDataView.byteLength); // 16
console.log(fullDataView.buffer === buf); // true// 构造函数接收一个可选的字节偏移量和字节长度
// byteOffset = 0 , 视图从缓冲起点开始
// byteLength = 8 , 视图限制为前 8 个字节
const firstHalfDataView = new DataView(buf, 0, 8);
console.log(firstHalfDataView.byteOffset); // 0
console.log(firstHalfDataView.byteLength); // 8
console.log(firstHalfDataView.buffer === buf); // trueconst secondHalfDataView = new DataView(buf, 8);
console.log(secondHalfDataView.byteOffset); // 8
console.log(secondHalfDataView.byteLength); // 8
console.log(secondHalfDataView.buffer === buf); // true

ElementType

ElementType 用来实现 JavaScript 的 Number 类型到缓冲内二进制格式的转换。

DataView 对存储在缓冲内的数据类型没有预设。它暴露的 API 强制开发者在读、写时指定一个 ElementType,然后 DataView 就会完成相应的转换。

ECMAScript 6 支持 8 种不同的 ElementType。

ElementType 字节 说明
Int8 1 8 位有符号整数
Uint8 1 8 位无符号整数
Int16 2 16 位有符号整数
Uint16 2 16 位无符号整数
Int32 4 32 位有符号整数
Uint32 4 32 位无符号整数
Float32 4 32 位 IEEE-754 浮点数
Float64 8 64 位 IEEE-754 浮点数

DataView 每种类型都暴露了 get 和 set 方法,这些方法使用 byteOffset(字节偏移量)定位要读取或写入值的位置,类型可以互换使用。

const buf = new ArrayBuffer(2); // 2 字节,16 位
const view = new DataView(buf);// 检查第 1 个和第 2 个字节
console.log(view.getInt8(0)); // 0
console.log(view.getInt8(1)); // 0// 检查整个缓冲
console.log(view.getInt16(0)); // 0// 将第 1 个和第 2 个字节设置为 1
// 255 的二进制表示是 11111111(2^8 - 1)
view.setUint8(0, 255);
// DataView 会自动将数据转换为特定的 ElementType,255 的十六进制表示是 0xFF
view.setUint8(1, 0xFF);// 缓冲里都是 1,如果把它当成二补数的有符号整数,则是 -1
console.log(view.getInt16(0)); // -1

字节序

“字节序”,指字节的排列顺序,是一种字节顺序的约定。计算机硬件有两种储存数据的方式:大端字节序(big endian)和小端字节序(little endian)。

大端字节序也称为“网络字节序”,高位(参考数学中的位)字节在前(低地址),低位字节在后(高地址),小端字节序正好相反,低位字节在前,高位字节在后。

比如:数值 0x2211 使用两个字节储存,大端字节序是 0x2211,小端字节序是 0x1122。

大端字节序符合人类读写数值的习惯,为什么会有小端字节序?

计算机处理字节序的时候,不知道什么是高位字节,什么是低位字节。它只知道按顺序读取字节(低地址到高地址),先读第一个字节,再读第二个字节。如果是大端字节序,先读到的就是高位字节,后读到的就是低位字节,小端字节序正好相反。

因为计算通常是从低位开始的,而计算时先处理低位字节,效率比较高,所以计算机内部处理都是小端字节序。

但是,人类习惯读写大端字节序,所以,除了计算机的内部处理,其他的场合几乎都是大端字节序,比如网络传输和文件储存。

JavaScript 运行时所在系统的原生字节序决定了如何读取或写入字节,但 DataView 并不遵守这个约定。对一段内存而言,DataView 是一个中立接口,它会遵循你指定的字节序。DataView 的所有 API 方法都以大端字节序作为默认值,但接收一个可选的布尔值参数,设置为 true 即可启用小端字节序。

// 在内存中分配两个字节并声明一个 DataView
const buf = new ArrayBuffer(2);
const view = new DataView(buf);// 填充缓冲,让第一位和最后一位都是1
view.setUint8(0, 0x80); // 设置最左边的 bit 位等于1
view.setUint8(1, 0x01); // 设置最右边的 bit 位等于1// 按大端字节序读取 Uint16
// 0x80 是高字节,0x01 是低字节,1000 0000 0000 0001
// 0x8001 = 2^15 + 2^0 = 32768 + 1 = 32769
console.log(view.getUint16(0)); // 32769// 按小端字节序读取 Uint16
// 0x01 是高字节,0x80 是低字节,0000 0001 1000 0000
// 0x0180 = 2^8 + 2^7 = 256 + 128 = 384
console.log(view.getUint16(0, true)); // 384// 按大端字节序写入 Uint16,0000 0000 0000 0100
view.setUint16(0, 0x0004);
console.log(view.getUint8(0)); // 0
console.log(view.getUint8(1)); // 4// 按小端字节序写入 Uint16,0000 0010 0000 0000
view.setUint16(0, 0x0002, true);
console.log(view.getUint8(0)); // 2
console.log(view.getUint8(1)); // 0

边界情形

DataView 完成读、写操作的前提是必须有充足的缓冲区,否则就会抛出 RangeError。

const buf = new ArrayBuffer(6);
const view = new DataView(buf);
// 尝试读取部分超出缓冲范围的值
view.getInt32(4); // RangeError// 尝试读取超出缓冲范围的值
view.getInt32(-1); // RangeError// 尝试写入超出缓冲范围的值
view.setInt32(4, 123); // RangeError

DataView 在写入缓冲时会尽量把值转换为适当的类型,后备为 0。如果无法转换,则抛出错误。

const buf = new ArrayBuffer(1);
const view = new DataView(buf);view.setInt8(0, 1.5);
console.log(view.getInt8(0)); // 1view.setInt8(0, [4]);
console.log(view.getInt8(0)); // 4view.setInt8(0, 'f');
console.log(view.getInt8(0)); // 0view.setInt8(0, Symbol()); // TypeError

定型数组视图

定型数组是另一种形式的 ArrayBuffer 视图。概念上与 DataView 接近,区别在于,它特定于一种 ElementType 且遵循系统原生的字节序。

相应地,定型数组提供了适用面更广的 API 和更高的性能。设计定型数组的目的就是提高与 WebGL 等原生库交换二进制数据的效率。由于定型数组的二进制表示对操作系统而言是一种容易使用的格式,JavaScript 引擎可以重度优化算术运算、按位运算和其他对定型数组的常见操作,因此速度极快。

类型 值范围 字节数 描述
Int8Array -128~127 1 8 位有符号整数(补码)
Uint8Array 0~255 1 8 位无符号整数
Uint8ClampedArray 0~255 1 8 位无符号整数(值会被裁剪)
Int16Array -32768~32767 2 16 位有符号整数(补码)
Uint16Array 0~65535 2 16 位无符号整数
Int32Array -2147483648~2147483647 4 32 位有符号整数(补码)
Uint32Array 0~4294967295 4 32 位无符号整数
Float32Array -3.4E38~3.4E38 以及 1.2E-38(最小正数) 4 32 位 IEEE 浮点数(7 位有效数字,例如 1.123456
Float64Array -1.8E308~1.8E308 以及 5E-324(最小正数) 8 64 位 IEEE 浮点数(16 位有效数字,例如 1.123…15)

创建定型数组

创建定型数组的方式包括读取已有的缓冲、使用自有缓冲、填充可迭代结构,以及填充基于任意类型的定型数组。另外,通过 <ElementType>.from()<ElementType>.of() 也可以创建定型数组。

// 创建一个 12 字节的缓冲
const buf = new ArrayBuffer(12);
// 创建一个引用该缓冲的 Int32Array
const ints = new Int32Array(buf);
// 这个定型数组知道自己的每个元素需要 4 字节,因此长度为 3
console.log(ints.length); // 3console.log(ints[0]); // 默认初始化为 0// 创建一个长度为 6 的 Int32Array
const ints2 = new Int32Array(6);
// 每个数值使用 4 字节,因此 ArrayBuffer 是 24 字节
console.log(ints2.length); // 6// 类似 DataView,定型数组也有一个指向关联缓冲的引用
console.log(ints2.buffer.byteLength); // 24// 创建一个包含 [2, 4, 6, 8] 的 Int32Array
const ints3 = new Int32Array([2, 4, 6, 8]);
console.log(ints3.length); // 4
console.log(ints3.buffer.byteLength); // 16
console.log(ints3[2]); // 6// 通过复制 ints3 的值创建一个 Int16Array
const ints4 = new Int16Array(ints3);
// 新类型数组会分配自己的缓冲,对应索引的每个值会相应地转换为新格式
console.log(ints4.length); // 4
console.log(ints4.buffer.byteLength); // 8
console.log(ints4[2]); // 6// 基于普通数组来创建一个 Int16Array
const ints5 = Int16Array.from([3, 5, 7, 9]);
console.log(ints5.length); // 4
console.log(ints5.buffer.byteLength); // 8
console.log(ints5[2]); // 7// 基于传入的参数创建一个 Float32Array
const floats = Float32Array.of(3.14, 2.718, 1.618);
console.log(floats.length); // 3
console.log(floats.buffer.byteLength); // 12
console.log(floats[2]); // 1.6180000305175781

定型数组的构造函数和实例都有 BYTES_PER_ELEMENT 属性,返回数组中每个元素的大小。

console.log(Int16Array.BYTES_PER_ELEMENT); // 2
console.log(Int32Array.BYTES_PER_ELEMENT); // 4const ints = new Int32Array(1),floats = new Float64Array(1);
console.log(ints.BYTES_PER_ELEMENT); // 4
console.log(floats.BYTES_PER_ELEMENT); // 8

定型数组行为

定型数组与普通数组很相似,支持如下操作符、方法和属性。

  • []
  • copyWithin()
  • entries()
  • every()
  • fill()
  • filter()
  • find()
  • findIndex()
  • forEach()
  • for…of
  • indexOf()
  • join()
  • keys()
  • lastIndexOf()
  • length
  • map()
  • reduce()
  • reduceRight()
  • reverse()
  • slice()
  • some()
  • sort()
  • toLocaleString()
  • toString()
  • values()

定型数组使用数组缓冲来存储数据,而数组缓冲无法调整大小。因此,下列方法不适用于定型数组。

  • concat()
  • pop()
  • push()
  • shift()
  • splice()
  • unshift()

不过,定型数组提供了两个新方法,可以快速向外或向内复制数据:set() 和 subarray()。

set() 从提供的数组或定型数组中把值复制到当前定型数组中指定的索引位置。

// 创建长度为 8 的 int16 数组
const container = new Int16Array(8); // 偏移量默认为索引 0
container.set(Int8Array.of(1, 2, 3, 4));
console.log(container); // [1,2,3,4,0,0,0,0]// 偏移量 4 表示从索引 4 开始插入
container.set([5,6,7,8], 4);
console.log(container); // [1,2,3,4,5,6,7,8]// 溢出会抛出错误
container.set([5,6,7,8], 7); // RangeError

subarray() 执行与 set() 相反的操作,它会基于从原始定型数组中复制的值返回一个新定型数组。复制值时的开始索引和结束索引是可选的。

const source = Int16Array.of(2, 4, 6, 8);
// 把整个数组复制为一个同类型的新数组
const fullCopy = source.subarray();
console.log(fullCopy); // [2, 4, 6, 8]// 从索引 2 开始复制数组
const halfCopy = source.subarray(2);
console.log(halfCopy); // [6, 8]// 从索引 1 开始复制到索引 3
const partialCopy = source.subarray(1, 3);
console.log(partialCopy); // [4, 6]

下溢和上溢

定型数组中值的下溢和上溢不会影响到其他索引,但仍然需要考虑数组的元素应该是什么类型。定型数组对于可以存储的每个索引只接受一个相关位,而不考虑它们对实际数值的影响。

// 长度为 2 的有符号整数数组
// 每个索引保存一个二补数形式的有符号整数
// 范围是 -128(-1 * 2^7)~127(2^7 - 1)
const ints = new Int8Array(2); // 长度为 2 的无符号整数数组
// 每个索引保存一个无符号整数
// 范围是 0~255(2^7 - 1)
const unsignedInts = new Uint8Array(2); // 无符号数组,上溢的位不会影响相邻索引
// 索引只取最低有效位上的 8 位
unsignedInts[1] = 256; // 0x100
console.log(unsignedInts); // [0, 0]
unsignedInts[1] = 511; // 0x1FF
console.log(unsignedInts); // [0, 255]// 无符号数组,下溢的位会被转换为其无符号的等价值
// 0xFF 是以二补数形式表示的 -1(截取到 8 位),
// 但 255 是一个无符号整数
unsignedInts[1] = -1 // 0xFF (truncated to 8 bits)
console.log(unsignedInts); // [0, 255]// 有符号数组,上溢自动变成二补数形式
// 0x80 是无符号整数的 128,是二补数形式的 -128
ints[1] = 128; // 0x80
console.log(ints); // [0, -128]// 有符号数组,下溢自动变成二补数形式
// 0xFF 是无符号整数的 255,是二补数形式的 -1
ints[1] = 255; // 0xFF
console.log(ints); // [0, -1]

除了 8 种元素类型,还有一种“夹板”数组类型:Uint8ClampedArray,不允许任何方向溢出。超出最大值 255 的值会被向下舍入为 255,而小于最小值 0 的值会被向上舍入为 0。

const clampedInts = new Uint8ClampedArray([-1, 0, 255, 256]);
console.log(clampedInts); // [0, 0, 255, 255]

按照 JavaScript 之父 Brendan Eich 的说法:“Uint8ClampedArray 完全是 HTML5 canvas 元素的
历史留存。除非真的做跟 canvas 相关的开发,否则不要使用它。”

JS Typed Array 定型数组相关推荐

  1. node.js下安装 webpack 的时候,出现:TypeError:this is not a typed array;

    在windows下安装webpack(前端打包工具,node环境已安装)的时候,控制台突然报了这个错误: C:\Users\admin\AppData\Roaming\npm\node_modules ...

  2. JS的Array.isArray来判断是否是数组

    Array.isArray 数组是基于对象的,不构成单独的语言类型. 所以 typeof 不能帮助从数组中区分出普通对象: Array.isArray(value).如果 value 是一个数组,则返 ...

  3. 理解 typed array

    我们知道在C语言中,可以使用malloc和free方法来分配和释放内存.随着web的发展中,js在ES6中新增了内存操作的支持.其实现方式就是---typed array. typed array是个 ...

  4. js中对arry数组的各种操作小结

    最近工作比较轻松,于是就花时间从头到尾的对js进行了详细的学习和复习,在看书的过程中,发现自己平时在做项目的过程中有很多地方想得不过全面,写的不够合理,所以说啊,为了在以后的工作中写出最优化的代码,我 ...

  5. 探讨JS合并两个数组的方法

    转载自  探讨JS合并两个数组的方法 我们在项目过程中,有时候会遇到需要将两个数组合并成为一个的情况. 比如: var a = [1,2,3]; var b = [4,5,6]; 有两个数组a.b,需 ...

  6. Js中Array对象

    Js中Array对象 JavaScript的Array对象是用于构造数组的全局对象,数组是类似于列表的高阶对象. 描述 在JavaScript中通常可以使用Array构造器与字面量的方式创建数组. c ...

  7. js for循环 遍历数组 遍历对象属性

    1.js for循环 遍历对象属性 var person = {fname:"John",lname:"Doe",age:25}; for (x in pers ...

  8. js 根据id获取数组中对应的对象

    js 根据id获取数组中对应的对象 const id = xxxlet obj = array.find(function (e) {return e.id=== id})console.log('o ...

  9. 使用vue脚手架的项目使用https: true,报错:Invalid typed array length: -4095

    使用vue脚手架的项目使用https: true,报错:Invalid typed array length: -4095 使用vue脚手架的项目使用https: true,报错:Invalid ty ...

最新文章

  1. 【干货】功能堆砌or视觉美观?看优秀PM如何权衡
  2. bootstrap全局css样式
  3. sybase游标使用方法
  4. python语言的优缺点论文_Python语言的优缺点是什么呢?
  5. lcs文本相似度_具有LCS方法的通用文本比较工具
  6. Linux共享文件夹中毒,linux服务器中毒利用Find查找病毒例子
  7. leetcode之回溯backtracing专题1
  8. 快速开平方取倒数的算法
  9. 05.SpringBoot的yml配置详解
  10. python是哪个专业学的-我们为什么要选择学习python?学习python有什么用?
  11. NSData的同步下载与NSConnection的同步下载
  12. Ubuntu18.04终端里,随意拖动或双击会出现ctrl+C的效果,解决
  13. 防止sql拼接的Java方法_JAVA程序防止SQL注入的方法
  14. 【吃豆游戏----HTML+JS+CSS等实现,效果+源代码】
  15. xml转json的两种方法
  16. 游戏反外挂的难点和破局之路
  17. 淘宝封杀返现模式 淘宝客返利网站模式遇挑战
  18. 金蝶K3 BOM独立控制跳层开关开发
  19. [Excel 与 股票] 一图胜千言之 Excel 处理股票数据
  20. python数据可视化案例2017年6省gdp_吴裕雄 数据挖掘与分析案例实战(5)——python数据可视化...

热门文章

  1. ajax什么是异步和同步,ajax异步和同步的区别
  2. php-xhprof 学习历程
  3. 微积分的本质计算机的应用
  4. Neo4j安装Apoc插件
  5. 平面设计和美工区别在哪,学平面设计和美工哪个难
  6. nodejs之处理POST请求
  7. Laya引擎生产力工具LayaTree
  8. CDN技术的发展历程
  9. 漫画:程序员为什么穿 “格子衫” ?
  10. Gly-Phe-Leu, 15373-56-5