JavaScript类型化数组(二进制数组)
0、前言
对于前端程序员来说,平时很少和二进制数据打交道,所以基本上用不到ArrayBuffer,大家对它很陌生,但是在使用WebGL的时候,ArrayBuffer无处不在。浏览器通过WebGL和显卡进行通信,它们之间会发生大量的、实时的数据交互,对性能的要求特别高,它们之间的数据通信必须是二进制的才能满足性能要求,而不能是传统的文本格式。文本格式传递一个 32 位整数,两端的 JavaScript 脚本与显卡都要进行格式转化,将非常耗时。类型化数组的诞生就是为了能够让开发者通过类型化数组来操作内存,大大增强了JavaScript处理二进制数据的能力。
JavaScript类型化数组将实现拆分为缓冲和视图两部分。一个缓冲(ArrayBuffer)描述的是内存中的一段二进制数据,缓冲没有格式可言,并且不提供机制访问其内容。为了访问在缓存对象中包含的内存,你需要使用视图。视图可以将二进制数据转换为实际有类型的数组。一个缓冲可以提供给多个视图进行读取,不同类型的视图读取的内存长度不同,读取出来的数据格式也不同。缓冲和视图的工作方式如下图所示:

1、缓冲(ArrayBuffer)和视图
ArrayBuffer是一个构造函数,可以分配一段可以存放数据的连续内存区域。
var buffer = new ArrayBuffer(8);
上面代码生成了一段8字节的内存区域,每个字节的值默认都是0。1 字节(Byte) = 8 比特(bit),1比特就是一个二进制位(0 或 1)。上面代码生成的8个字节的内存区域,一共有 8*8=64 比特,每一个二进制位都是0。
为了读写这个buffer,我们需要为它指定视图。视图有两种,一种是TypedArray视图,它一共包括9种类型,还有一种是DataView视图,它可以自定义复合类型。 基础用法如下:
var dataView = new DataView(buffer);
dataView.getUint8(0) // var int32View = new Int32Array(buffer);
int32View[0] = 1 // 修改底层内存 var uint8View = new Uint8Array(buffer);
uint8View[0] // 1
| 视图类型 | 说明 | 字节大小 |
| Uint8Array | 8位无符号整数 | 1字节 |
| Int8Array | 8位有符号整数 | 1字节 |
| Uint8ClampedArray | 8位无符号整数(溢出处理不同) | 1字节 |
| Uint16Array | 16位无符号整数 | 2字节 |
| Int16Array | 16位有符号整数 | 2字节 |
| Uint32Array | 32位无符号整数 | 4字节 |
| Int32Array | 32位有符号整数 | 4字节 |
| Float32Array | 32位IEEE浮点数 | 4字节 |
| Float64Array | 64位IEEE浮点数 | 8字节 |
下面来看一个完整的例子:
// 创建一个16字节长度的缓冲
var buffer = new ArrayBuffer(16);
// 创建一个视图,此视图把缓冲内的数据格式化为一个32位(4字节)有符号整数数组
var int32View = new Int32Array(buffer);
// 我们可以像普通数组一样访问该数组中的元素
for (var i = 0; i < int32View.length; i++) {
int32View[i] = i * 2;
}
// 运行完之后 int32View 为[0,2,4,6]
// 创建另一个视图,此视图把缓冲内的数据格式化为一个16位(2字节)有符号整数数组
var int16View = new Int16Array(buffer); for (var i = 0; i < int16View.length; i++) {
console.log(int16View[i]);
}
// 打印出来的结果依次是0,0,2,0,4,0,6,0

相信图片已经很直观的表达了这段代码的意思。这里应该有人会疑问,为什么2、4、6这三个数字会排在0的前面,这是因为x86的系统都是使用的小端字节序来存储数据的,小端字节序就是在内存中,数据的高位保存在内存的高地址中,数据的低位保存在内存的低地址中。就拿上面这段代码举例,上图中内存大小排列的顺序是从左向右依次变大,int32View[1]对应的4个字节,它填入的值是 10 (2的2进制表示),把0补齐的话就是 00000000 00000000 00000000 00000010(中间的分隔方便观看),计算机会倒过来填充,最终会成为 00000010 00000000 00000000 00000000。与小端字节序对应的就是大端字节序,它就是我们平时读数字的顺序。
2、实际场景
在WebGL中有这么一个需求,我要绘制一个带颜色的三角形,这个三角形有三个顶点,每个点有3个坐标和一个RGBA颜色,现在有了三角形的顶点和颜色数据,需要创建一个缓冲,把三角形的数据按顺序填入,然后传输给WebGL。目前的三角形数据是这样的:
var triangleVertices = [
// (x, y, z) (r, g, b, a)
0.0, 0.5, 0.0, 255, 0, 0, 255, // V0
0.5, -0.5, 0.0, 0, 250, 6, 255, // V1
-0.5, -0.5, 0.0, 0, 0, 255, 255 // V2
];
目标格式是一个ArrayBuffer,它的格式是这样的:

表示坐标的浮点数是32位的,占4个字节,表示颜色的正整数是8位的,占1个字节,因此我们需要创建两个视图来对这个缓冲进行赋值。
var triangleVertices = [
// (x, y, z) (r, g, b, a)
0.0, 0.5, 0.0, 255, 0, 0, 255, // V0
0.5, -0.5, 0.0, 0, 250, 6, 255, // V1
-0.5, -0.5, 0.0, 0, 0, 255, 255 // V2
]; var nbrOfVertices = 3; // 顶点数量
var vertexSizeInBytes = 3 * Float32Array.BYTES_PER_ELEMENT + 4 * Uint8Array.BYTES_PER_ELEMENT; // 一个顶点所占的字节数 3*4+4*1 = 16 var buffer = new ArrayBuffer(nbrOfVertices * vertexSizeInBytes); // 3 * 16 = 48 三个顶点一共需要的字节数
var positionView = new Float32Array(buffer);
var colorView = new Uint8Array(buffer); var positionOffsetInFloats = 0;
var colorOffsetInBytes = 12;
var k = 0;
// 用三角形数据填充arrayBuffer
for (var i = 0; i < nbrOfVertices; i++) {
positionView[positionOffsetInFloats] = triangleVertices[k]; // x
positionView[1 + positionOffsetInFloats] = triangleVertices[k + 1]; // y
positionView[2 + positionOffsetInFloats] = triangleVertices[k + 2]; // z
colorView[colorOffsetInBytes] = triangleVertices[k + 3]; // r
colorView[1 + colorOffsetInBytes] = triangleVertices[k + 4]; // g
colorView[2 + colorOffsetInBytes] = triangleVertices[k + 5]; // b
colorView[3 + colorOffsetInBytes] = triangleVertices[k + 6]; // a positionOffsetInFloats += 4; // 4个字节的浮点数循环一次要偏移4位
colorOffsetInBytes += 16; // 1个字节的整数循环一次要偏移16位
k += 7; // 原数组一次处理七个数值(三个坐标四个颜色)
}
这段代码运行完,就可以得到我们想要的ArrayBuffer。希望大家可以在浏览器控制台运行一下,然后看看positionView和colorView里面的数据验证一下。细心的小伙伴会发现,如果使用positionView访问颜色数据,或者colorView访问位置数据,得到的数据是“奇怪”的,不知道原因的读者朋友可以去了解一下原码、补码、IEEE浮点数相关的知识。
3、总结
类型化数组的内容还有很多,在这里我只重点介绍了一下缓冲和视图是如何一起合作来管理内存的。
类型化数组的出现最大的作用就是提升了数组的性能,js中Array的内部实现是链表,可以动态增大减少元素,但是元素多的话,性能会比较差,类型化数组管理的是连续内存区域,知道了这块内存的起始位置,可以通过起始位置+N * 偏移量(一次加法一次乘法操作)访问到第N个位置的元素,而Array的话就需要通过链表一个一个的找下去。
类型化数组的使用场景并不多,可以说是为WebGL量身定做的,不过还是希望你能在以后遇到大量数据的场景能够想起来JS的类型化数组这个功能。
JavaScript类型化数组(二进制数组)的更多相关文章
- JavaScript jQuery 中定义数组与操作及jquery数组操作
首先给大家介绍javascript jquery中定义数组与操作的相关知识,具体内容如下所示: 1.认识数组 数组就是某类数据的集合,数据类型可以是整型.字符串.甚至是对象Javascript不支持多 ...
- Javascript Jquery 中的数组定义与操作_子木玲_新浪博客
body{ font-family: "Microsoft YaHei UI","Microsoft YaHei",SimSun,"Segoe UI& ...
- ES6 二进制数组
二进制数组(ArrayBuffer对象.TypedArray视图和DataView视图)是JavaScript操作二进制数据的一个接口.这些对象早就存在,属于独立的规格(2011年2月发布),ES6将 ...
- JavaScript jQuery 中定义数组与操作及jquery数组操作 http://www.jb51.net/article/76601.htm
首先给大家介绍javascript jquery中定义数组与操作的相关知识,具体内容如下所示: 1.认识数组 数组就是某类数据的集合,数据类型可以是整型.字符串.甚至是对象Javascript不支持多 ...
- Javascript基础四(数组,字符,对象,日期)
第一节:数组 1.数组的概念及定义 可以存放一组数据: 当需要操作多个数据时: 2.数组的创建方式 var arr1 = [1,2,3]; //字面量方式 var arr2 ...
- es6二进制数组--基础
一.概念二进制数组由 ArrayBuffer对象 TypeArray 视图和DataView视图 三部分组成是javascript操作二进制数据的一个接口. 早在2011年2月就已经发布,但是由于ES ...
- JavaScript中常见的数组操作函数及用法
JavaScript中常见的数组操作函数及用法 昨天写了个帖子,汇总了下常见的JavaScript中的字符串操作函数及用法.今天正好有时间,也去把JavaScript中常见的数组操作函数及用法总结一下 ...
- JavaScript面向对象程序设计:数组
或许你会奇怪,面向对象的程序设计为什么从数组开始讲起?这是因为……其间的种种关系吧……嘿嘿,这里先卖个关子,先来看看我们熟悉的数组在JavaScript里面是什么样子的. 1. 创建数组 在J ...
- javascript中的稀疏数组(sparse array)和密集数组
学习underscore.js数组相关API的时候.遇到了sparse array这个东西,曾经没有接触过. 这里学习下什么是稀疏数组和密集数组. 什么是密集数组呢?在java和C语言中,数组是一片连 ...
随机推荐
- ABP入门系列(17)——使用ABP集成的邮件系统发送邮件
ABP中对邮件的封装主要集成在Abp.Net.Mail和Abp.Net.Mail.Smtp命名空间下,相应源码在此. #一.Abp集成的邮件模块是如何实现的 分析可以看出主要由以下几个核心类组成: E ...
- Go语言数组和切片的原理
目录 数组 创建 访问和赋值 切片 结构 初始化 访问 追加 拷贝 总结 数组和切片是 Go 语言中常见的数据结构,很多刚刚使用 Go 的开发者往往会混淆这两个概念,数组作为最常见的集合在编程语言中是 ...
- [Swift]LeetCode64. 最小路径和 | Minimum Path Sum
Given a m x n grid filled with non-negative numbers, find a path from top left to bottom right which ...
- [Swift]LeetCode65. 有效数字 | Valid Number
Validate if a given string is numeric. Some examples:"0" => true" 0.1 " => ...
- TensorFlow tf.gradients的用法详细解析以及具体例子
tf.gradients 官方定义: tf.gradients( ys, xs, grad_ys=None, name='gradients', stop_gradients=None, ) Cons ...
- RedirectToAction()转移方式及参数传递
今天在做一个功能的时,使用RedirectToAction()需要从这里传几个参数,从网上查了一下,这样解决.真好. Return RedirectToAction("Index" ...
- 关于QT下资源使用和资源占用…
原文地址:关于QT下资源使用和资源占用内存过多的问题作者:技术成就梦想 最近研究了一下如何从外部动态调用图片的问题,从而研究了图片资源的使用方法.网上最常见的帖子是这个,感觉总结的还不错. h ...
- rabbitMQ linux安装
1,下载 erlang (http://www.rabbitmq.com/releases/erlang/) wget http://www.rabbitmq.com/releases/erlang/ ...
- 客户端缓存机制 - Cookie详解
Cookie 作者:Stanley 罗昊 [转载请注明出处和署名,谢谢!] Cookie不是内置对象,所以用的时候需要new出来,Cookie是由服务端产生的,再发送给客户端保存,它不是内置对象,却是 ...
- Spring Boot(六)集成 MyBatis 操作 MySQL 8
一.简介 1.1 MyBatis介绍 MyBatis 是一款优秀的持久层框架,它支持定制化 SQL.存储过程以及高级映射.MyBatis 避免了几乎所有的 JDBC代码和手动设置参数以及获取结果集. ...