Threejs拓展之⼆进制数组
在Threejs 的学习过程中,分配缓存区域时需要调⽤JavaScript中的Uint16Array、Float32Array等对象来分配连续的内存空间。看到Uint16Array、
Float32Array时,感觉之前学了假的JavaScript。查资料发现,《ES6标准⼊门 第⼆版》的第⼗⼆章⼆进制数组 详细的介绍了上⾯的⼏个对象。
⼆进制数组
⼆进制数组(ArrayBuffer 对象、TypedArray 视图和 DataView 视图)是JavaScript操作⼆进制数据的⼀个接⼝。这些对象早就存在,属于独⽴的规格(2011年2⽉发布),ES6将它们纳⼊ECMAScript规格,并增加了新的⽅法。
这个接⼝的原始设计⽬的,与WebGL项⽬有关。所谓WebGL,就是指浏览器与显卡之间的通信接⼝,为了满⾜JavaScript与显卡之间⼤量的、实时的数据交换,它们之间的数据通信必须是⼆进制的,⽽不能是传统的⽂本格式。⽂本格式传递⼀个32位整数,两端的JavaScript脚本与显卡都要进⾏格式转化,将⾮常耗时。这时要是存在⼀种机制,类似C语⾔,直接操作字节,将4个字节的32位整数,以⼆进制形式原封不动地送⼊显卡,脚本的性能将会⼤幅提升。
⼆进制数组就是在这种背景下诞⽣的。它很像C语⾔的数组,允许开发者以数组下标的形式,直接操作内存,⼤⼤增强了JavaScript处理⼆进制数据的能⼒,使得开发者有可能通过JavaScript与操作系统的原⽣接⼝进⾏⼆进制通信。
⼆进制数组由三类对象组成:
1-- ArrayBuffer 对象:代表内存之中的⼀段⼆进制数据,可以通过“视图”进⾏操作。“视图”部署了数组接⼝,这也就是说,可以⽤数组的⽅法操作内存。
2-- TypedArray 视图:共包括9种类型的视图,⽐如Uint8Array数组视图、Int16Array数组视图、Float32Array数组视图等。
3-- DataView 视图:可以⾃定义复合格式的视图,⽐如第⼀个字节是Uint8,第⼆、三个字节是Int6、第四个字节是Float32等等,此外还可以⾃定义字节序。
简单说,ArrayBuffer 对象代表原始的⼆进制数据,TypedArray 视图⽤来读写简单类型的⼆进制数据,DataView 视图⽤来读写复杂类型的⼆进制数据。
TypedArray 视图⽀持的数据类型⼀共有9种,DataView 视图⽀持除Uint8C以外的其他8种。
注意:⼆进制数组并不是真正的数组,⽽是类似数组的对象。
很多浏览器操作的API,⽤到了⼆进制数组操作⼆进制数据,⽐如:File API、XML HTTPRequest、Fetch API、Canvas、WebSockets。ArrayBuffer 对象
ArrayBuffer 对象代表储存⼆进制数据的⼀段内存,它不能直接读写,只能通过视图(TypedArray 视图和 DataView 视图)来读写,视图的作⽤是以指定格式解读⼆进制数据。
ArrayBuffer 也是⼀个构造函数,可以分配⼀段可以存放数据的连续内存区域。
var buf = new ArrayBuffer(32);
上⾯代码⽣成了⼀段32字节的内存区域,每个字节的值默认都是0。可以看到,ArrayBuffer 构造函数的参数是所需要的内存⼤⼩(单位字节)。
为了读写这段内容,需要为它指定视图。DataView 视图的创建,需要提供ArrayBuffer 对象实例作为参数。
var buf = new ArrayBuffer(32);
var dataView = new DataView(buf);
上⾯代码对⼀段32字节的内存,建⽴DataView视图,然后以不带符号的8位整数格式,读取第⼀个元素,结果得到0,因为原始内存的ArrayBuffer 对象默认所有位都是0。
另⼀种 TypedArray 视图,与 DataView 视图的⼀个区别是,它不是⼀个构造函数,⽽是⼀组构造函数,代表不同的数据格式。
var buffer = new ArrayBuffer(12);
var x1 = new Int32Array(buffer);
x1[0] = 1;
var x2 = new Uint8Array(buffer);
x2[0] = 2;
console.log(x1[0]); //2
上⾯代码对同⼀段内存,分别建⽴两种视图:32位带符号整数(Int32Array 构造函数)和8位不带符号整数(Uint8Array 构造函数)。由于两个视图对应的是同⼀段内存,⼀个视图修改底层内存,会影响到另⼀个视图。
TypedArray 视图的构造函数,除了接受 ArrayBuffer 实例作为参数,还可以接受普通数组作为参数,直接分配内存⽣成底层的ArrayBuffer实例,并同时完成对这段内存的赋值。
var typedArray = new Uint8Array([0,1,2]);
console.log(typedArray.length); //3
typedArray[0] = 5;
console.log(typedArray); //[5,1,2]
上⾯代码使⽤TypedArray 视图的Uint8Array 构造函数,新建⼀个不带符号的8位整数视图。可以看到,Uint8Array 直接使⽤普通数组作为参数,对底层内存的赋值同时完成。
ArrayBuffer.prototype.byteLength
ArrayBuffer 实例的 byteLength 属性,返回所分配的内存区域的字节长度。
var buffer = new ArrayBuffer(32);
console.log(buffer.byteLength); //32
如果要分配的内存区域很⼤,有可能分配失败(可能没有那么多的连续空余内存),所以有必要检查是否分配成功。
if(buffer.byteLength === n) {
// 成功
} else {
// 失败
}
ArrayBuffer.prototype.slice()
ArrayBuffer 实例有⼀个 slice ⽅法,允许将内存区域的⼀部分,拷贝⽣成⼀个新的 ArrayBuffer 对象。
var buffer = new ArrayBuffer(8);
var newBuffer = buffer.slice(0, 3);
上⾯代码拷贝 buffer 对象的前3个字节(从0开始,到第3个字节前结束),⽣成⼀个新的 ArrayBuffer 对象。slice ⽅法其实包括两步,第⼀步是先分配⼀段内存,第⼆步是将原来那个 ArrayBuffer 对象拷贝进去。
slice ⽅法接受两个参数,第⼀个参数表⽰拷贝开始的字节序号(含该字节),第⼆个参数表⽰拷贝截⽌的字节序号(不含该字节)。如果省略第⼆个参数,则默认到原 ArrayBuffer 对象的结尾。
除了slice ⽅法,ArrayBuffer 对象不提供任何直接读写内存的⽅法,只允许在其上⽅建⽴视图,然后通过视图读写。
ArrayBuffer.isView()
ArrayBuffer 有⼀个静态⽅法isView,返回⼀个布尔值,表⽰参数是否为 ArrayBuffer的视图实例。这个⽅法⼤致相当于判断参数,是否为 TypedArray 实例或 DataView 实例。
var buffer = new ArrayBuffer(8);
ArrayBuffer.isView(buffer); //false
var v = new Int32Array(buffer);
ArrayBuffer.isView(v); //true
TypedArray 视图
ArrayBuffer 对象作为内存区域,可以存放多种类型的数据。同⼀段内存,不同数据有不同的解读⽅式,这就叫做“视图”(view)。ArrayBuffer 有两种视图,⼀种是 TypedArray 视图,⼀种是 DataView 视图。前者的数组成员都是同⼀个数据类型,后者的数组成员可以使不同的数组类型。
⽬前,TypedArray 视图⼀共包括9种类型,每⼀种视图都是⼀种构造函数:
·
Int8Array:8位有符号整数,长度1个字节。
· Uint8Array:8位⽆符号整数,长度1个字节。
· Uint8ClampedArray:8位⽆符号整数,长度1个字节,溢出处理不同。
· Int16Array:16位有符号整数,长度2个字节。
· Uint16Array:16位⽆符号整数,长度2个字节。
· Int32Array:32位有符号整数,长度4个字节。
· Uint32Array:32位⽆符号整数,长度4个字节。
· Float32Array:32位浮点数,长度4个字节。
· Float64Array:64位浮点数,长度8个字节。
这9个构造函数⽣成的数组,统称为 TypedArray 视图。它们很像普通数组,都有length属性,都能⽤⽅括号运算符([ ])获取单个元素,所有数组的⽅法,在它们上⾯都能使⽤。普通数组与 TypedArray 数组的差异主要在⼀下⽅⾯:
· TypedArray 数组的所有成员,都是同⼀种类型。
· TypedArray 数组的成员是连续的,不会有空位。
· TypedArray 数组成员的默认值为0。⽐如,new Array(10)返回⼀个普通数组,⾥⾯没有任何成员,只是10个空位;new Uint8Array(10) 返回⼀个TypedArray 数组,⾥⾯10个成员都是0。
· TypedArray 数组只是⼀层视图,本⾝不储存数据,它的数据都储存在底层的 ArrayBuffer 对象之中,要获取底层对象必须使⽤ buffer 属性。
构造函数
TypedArray 数组提供9种构造函数,⽤来⽣成相应类型的数组实例。
构造函数有多种⽅法:
1> TypedArray(buffer, byteOffset=0, length?)
同⼀个 ArrayBuffer 对象之上,可以根据不同的数据类型,建⽴多个视图。
// 创建⼀个8字节的ArrayBuffer
var b = new ArrayBuffer(8);
// 创建⼀个指向b的Int32视图,开始于字节0,直到缓冲区的末尾
var v1 = new Int32Array(b);
// 创建⼀个指向b的Uint8视图,开始于字节2,直到缓冲区的末尾
var v2 = new Uint8Array(b, 2);
// 创建⼀个指向b的Int16视图,开始于字节2,长度为2
var v3 = new Int16Array(b, 2, 2);
上⾯代码在⼀段长度为8个字节的内存(b)之上,⽣成了3个视图:v1、v2、v3。
视图的构造函数可以接受三个参数:
· 第⼀参数 -- 必需:视图对应的底层 ArrayBuffer 对象。
· 第⼆参数 -- 可选:视图开始的字节序号,默认从0开始。
·
第三参数 -- 可选:视图包含的数据个数,默认直到本段内存区域结束。
So,v1、v2和v3是重叠的:v1[0]是⼀个32位整数,指向字节0--字节3;v2[0]是⼀个8位⽆符号整数,指向字节2;v3[0]是⼀个16位整数,指向字节2--字节3。只要任何⼀个视图对内存有所修改,就会在另外两个视图上反映出来。
注意:byteOffset 必须与所要建⽴的数据类型⼀致,否则会报错。
var buffer = new ArrayBuffer(8);
var i16 = new Int16Array(buffer, 1);
//Uncaught RangeError: start offset of Int16Array should be a multiple of 2
上⾯代码中,新⽣成⼀个8个字节的 ArrayBuffer 对象,然后在这个对象的第⼀个字节,建⽴带符号的16位整数视图,结果报错。因为,带符号的16位整数需要两个字节,所以 byteOffset 参数必须能够被2整除。
如果想从任意字节开始解读 ArrayBuffer 对象,必须使⽤ DataView 视图,因为 TypedArray 视图只提供9种固定的解读格式。
2> TypedArray(length)
视图还可以不通过 ArrayBuffer 对象,直接分配内存⽽⽣成。
var f64a = new Float64Array(8);
f64a[0] = 10;
f64a[1] = 20;
f64a[2] = f64a[0] + f64a[1]; //30
上⾯代码⽣成⼀个8个成员的 Float64Array 数组(共64个字节),然后依次对每个成员赋值。这时,视图构造函数的参数就是成员的个数。可以看到,视图数组的赋值操作与普通数组的操作并⽆两样。
3> TypedArray(typedArray)
TypedArray 数组的构造函数,可以接受另⼀个TypedArray 实例作为参数:
var typedArray = new Int8Array(new Uint8Array(4));
上⾯代码中,Int8Array 构造函数接受⼀个 Uint8Array 实例作为参数。
注意,此时⽣成的新数组,只是复制了参数数组的值,对应的底层内存是不⼀样的。新数组会开辟⼀段新的内存储存数据,不会在原数组的内存之上建⽴视图。
var x = new Int8Array([1, 1]);
var y = new Int8Array(x);
console.log(x[0]); //1
console.log(y[0]); //1
x[0] = 2;
console.log(y[0]); //1
上⾯代码中,数组y是以数组x为模板⽽⽣成的,当x变动的时候,y并没有变动。
如果想基于同⼀段内存,构造不同的视图,可以采⽤如下的⽅法。
var x = new Int8Array([1, 1]);原生js和js的区别
var y = new Int8Array(x.buffer);
console.log(x[0]); //1
console.log(y[0]); //1
x[0] = 2;
console.log(y[0]); //2
4> TypedArray(arrayLikeObject)
构造函数的参数也可以是⼀个普通数组,然后直接⽣成 TypedArray 实例。
var typedArray = new Uint8Array([1,2,3,4]);
注意,这时 TypedArray 视图会重新开辟内存,不会在原数组的内存上建⽴视图。上⾯代码从⼀个普通的数组,⽣成⼀个8位⽆符号整数的 TypedArray 实例。TypedArray 数组也可以转换回普通数组。
var normalArray = Array.prototype.slice.call(typedArray);
数组⽅法
普通数组的操作⽅法和属性,对 TypedArray 数组完全适⽤。
· pyWithin( target, start[, end=this.length] )· ies( )
· TypedArray.prototype.every( callbackfn, thisArg? )
· TypedArray.prototype.fill( value, start=0, end=this.length )
· TypedArray.prototype.filter( callbackfn, thisArg? )
· TypedArray.prototype.find( predicate, thisArg? )
· TypedArray.prototype.findInex( predicate, thisArg? )
· TypedArray.prototype.forEach( callbackfn, thisArg? )
· TypedArray.prototype.indexOf( searchElement, fromIndex=0 )
· TypedArray.prototype.join( separator )
· TypedArray.prototype.keys( )
版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。
发表评论