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类型化数组(二进制数组)的更多相关文章

  1. JavaScript jQuery 中定义数组与操作及jquery数组操作

    首先给大家介绍javascript jquery中定义数组与操作的相关知识,具体内容如下所示: 1.认识数组 数组就是某类数据的集合,数据类型可以是整型.字符串.甚至是对象Javascript不支持多 ...

  2. Javascript Jquery 中的数组定义与操作_子木玲_新浪博客

    body{ font-family: "Microsoft YaHei UI","Microsoft YaHei",SimSun,"Segoe UI& ...

  3. ES6 二进制数组

    二进制数组(ArrayBuffer对象.TypedArray视图和DataView视图)是JavaScript操作二进制数据的一个接口.这些对象早就存在,属于独立的规格(2011年2月发布),ES6将 ...

  4. JavaScript jQuery 中定义数组与操作及jquery数组操作 http://www.jb51.net/article/76601.htm

    首先给大家介绍javascript jquery中定义数组与操作的相关知识,具体内容如下所示: 1.认识数组 数组就是某类数据的集合,数据类型可以是整型.字符串.甚至是对象Javascript不支持多 ...

  5. Javascript基础四(数组,字符,对象,日期)

    第一节:数组 1.数组的概念及定义     可以存放一组数据:          当需要操作多个数据时: 2.数组的创建方式 var arr1 = [1,2,3]; //字面量方式 var arr2 ...

  6. es6二进制数组--基础

    一.概念二进制数组由 ArrayBuffer对象 TypeArray 视图和DataView视图 三部分组成是javascript操作二进制数据的一个接口. 早在2011年2月就已经发布,但是由于ES ...

  7. JavaScript中常见的数组操作函数及用法

    JavaScript中常见的数组操作函数及用法 昨天写了个帖子,汇总了下常见的JavaScript中的字符串操作函数及用法.今天正好有时间,也去把JavaScript中常见的数组操作函数及用法总结一下 ...

  8. JavaScript面向对象程序设计:数组

    或许你会奇怪,面向对象的程序设计为什么从数组开始讲起?这是因为……其间的种种关系吧……嘿嘿,这里先卖个关子,先来看看我们熟悉的数组在JavaScript里面是什么样子的.   1. 创建数组   在J ...

  9. javascript中的稀疏数组(sparse array)和密集数组

    学习underscore.js数组相关API的时候.遇到了sparse array这个东西,曾经没有接触过. 这里学习下什么是稀疏数组和密集数组. 什么是密集数组呢?在java和C语言中,数组是一片连 ...

随机推荐

  1. 自学Python的经验之谈,学好Python的捷径

    其实python非常适合初学者入门.相比较其他不少主流编程语言,有更好的可读性,因此上手相对容易.自带的各种模块加上丰富的第三方模块,免去了很多“重复造轮子”的工作,可以更快地写出东西.配置开发环境也 ...

  2. MySQL乐观锁为什么可以防止并发

    问题引入 本文介绍的是最常用的也是mysql默认的innoDB引擎 Read committed隔离级别下事物的并发.这种情况下的事物特点是 读:在一个事物里面的select语句 不会受到其他事物(不 ...

  3. [Swift]LeetCode339. 嵌套链表权重和 $ Nested List Weight Sum

    Given a nested list of integers, return the sum of all integers in the list weighted by their depth. ...

  4. [Swift]LeetCode524. 通过删除字母匹配到字典里最长单词 | Longest Word in Dictionary through Deleting

    Given a string and a string dictionary, find the longest string in the dictionary that can be formed ...

  5. [Swift]LeetCode729. 我的日程安排表 I | My Calendar I

    Implement a MyCalendar class to store your events. A new event can be added if adding the event will ...

  6. [Swift]LeetCode834. 树中距离之和 | Sum of Distances in Tree

    An undirected, connected tree with N nodes labelled 0...N-1 and N-1 edges are given. The ith edge co ...

  7. [Swift]LeetCode922.按奇偶排序数组 II | Sort Array By Parity II

    Given an array A of non-negative integers, half of the integers in A are odd, and half of the intege ...

  8. [Swift]LeetCode1009. 十进制整数的补码 | Complement of Base 10 Integer

    Every non-negative integer N has a binary representation.  For example, 5 can be represented as &quo ...

  9. mybatis xml < >

    [参考文章]:mybatis 中的 xml 配置文件中 ‘<’. ‘>’ 处理 1.使用转义字符将 ‘<’. ‘>’ 替换掉 描述 字符 转义字符小于号 < <大于 ...

  10. WebSocket(3)---实现一对一聊天功能

    实现一对一聊天功能 功能介绍:实现A和B单独聊天功能,即A发消息给B只能B接收,同样B向A发消息只能A接收. 本篇博客是在上一遍基础上搭建,上一篇博客地址:[WebSocket]---实现游戏公告功能 ...