JavaScript < ES5、ES6、ES7、… >ECMAScript 6

ES6(十一)定型数组

2019-01-25  本文已影响19人  CodeMT

前面的话


  定型数组是一种用于处理数值类型(正如其名,不是所有类型)数据的专用数组,最早是在WebGL中使用的,WebGLOpenGL ES 2.0的移植版,在Web 页面中通过<canvas>元素来呈现它。定型数组也被一同移植而来,其可为JS提供快速的按位运算。本文将详细介绍ES6定型数组

概述

  在JS中,数字是以64位浮点格式存储的,并按需转换为32位整数,所以算术运算非常慢,无法满足WebGL的需求。因此在ES6中引入定型数组来解决这个问题,并提供更高性能的算术运算。所谓定型数组,就是将任何数字转换为一个包含数字比特的数组,随后就可以通过我们熟悉的JS数组方法来进一步处理

  ES6采用定型数组作为语言的正式格式来确保更好的跨JS引擎兼容性以及与JS数组的互操作性。尽管ES6版本的定型数组与WebGL中的不一样,但是仍保留了足够的相似之处,这使得ES6版本可以基于WebGL版本演化而不至于走向完全分化

【数值数据类型】

JS数字按照IEEE 754标准定义的格式存储,也就是用64个比特来存储一个浮点形式的数字。这个格式用于表示JS中的整数及浮点数,两种格式间经常伴随着数字改变发生相互转换。定型数组支持存储和操作以下8种不同的数值类型

无符号的8位整数(uint8)
有符号的16位整数(int16)
无符号的16位整数(uint16)
有符号的32位整数(int32)
无符号的32位整数(uint32)
32位浮点数(float32)
64位浮点数(float64)

【数组缓冲区】

数组缓冲区是所有定型数组的根基,它是一段可以包含特定数量字节的内存地址。创建数组缓冲区的过程类似于在C语言中调用malloc()来分配内存,只是不需指明内存块所包含的数据类型。可以通过ArrayBuffer构造函数来创建数组缓冲区

let buffer = New ArrayBuffer(10) // 分配10字节
let buffer = new ArrayBuffer(10); // 分配了 10 个字节
console.log(buffer.byteLength); // 10
let buffer = new ArrayBuffer(10); // 分配了 10 个字节
let buffer2 = buffer.slice(4, 6);
console.log(buffer2.byteLength); // 2

[注意]数组缓冲区包含的实际字节数量在创建时就已确定,可以修改缓冲区内的数据,但是不能改变缓冲区的尺寸大小

视图操作

数组缓冲区是内存中的一段地址,视图是用来操作内存的接口。视图可以操作数组缓冲区或缓冲区字节的子集,并按照其中一种数值型数据类型来读取和写入数据。DataView类型是一种通用的数组缓冲区视图,其支持所有8种数值型数据类型,要使用DataView,首先要创建一个ArrayBuffer实例,然后用这个实例来创建新的Dataview

let buffer = new ArrayBuffer(10),
view = new DataView(buffer);
let buffer = new ArrayBuffer(10),
view = new DataView(buffer, 5, 2); // 包含位置 5 与位置 6 的字节

【获取视图信息】

可以通过以下几种只读属性来获取视图的信息

buffer视图绑定的数组缓冲区
byteOffset DataView构造函数的第二个参数,默认是0,只有传入参数时才有值
byteLength DataView构造函数的第三个参数,默认是缓冲区的长度byteLength</pre>
  通过这些属性,可以查看视图正在操作缓冲区的哪一部分

let buffer = new ArrayBuffer(10),
view1 = new DataView(buffer), // 包含所有字节
view2 = new DataView(buffer, 5, 2); // 包含位置 5 与位置 6 的字节
console.log(view1.buffer === buffer); // true
console.log(view2.buffer === buffer); // true
console.log(view1.byteOffset); // 0
console.log(view2.byteOffset); // 5
console.log(view1.byteLength); // 10
console.log(view2.byteLength); // 2

【读取和写入数据】

JS有8种数值型数据类型,对于其中的每一种,都能在DataView的原型上找到相应的在数组缓冲区中写入数据和读取数据的方法。这些方法名都以setget打头,紧跟着的是每一种数据类型的缩写。例如,以下这个列表是用于读取和写入int8unit8类型数据的方法

getInt8(byteOffset,littleEndian)读取位于byteOffset后的int8类型数据
setInt8(byteOffset, value, littleEndian)byteOffset 处写入int8类型数据
getUint8(byteOffset, littleEndian)读取位于byteOffset 后的uint8类型数据
setUint8(byteOffset, value, littleEndian)byteOffset处写入uint8类型数据

getFloat32(byteOffset, littleEndian)读取位于byteOffset后的float32类型数据
setFloat32(byteOffset,value,littleEndian)byteOffset处写入float32类型数据
getFloat64(byteOffset,littleEndian) 读取位于byteOffset后的float64类型数据
setFloat64(byteOffset,value,littleEndian)byteOffset处写入float64类型数据

以下示例分别展示了setget方法的实际运用

let buffer = new ArrayBuffer(2),
view = new DataView(buffer);
view.setInt8(0, 5);
view.setInt8(1, -1);
console.log(view.getInt8(0)); // 5
console.log(view.getInt8(1)); // -1
let buffer = new ArrayBuffer(2),
view = new DataView(buffer);
view.setInt8(0, 5);
view.setInt8(1, -1);
console.log(view.getInt16(0)); // 1535
console.log(view.getInt8(0)); // 5
console.log(view.getInt8(1)); // -1
new ArrayBuffer(2)   0000000000000000
view.setInt8(0, 5);  0000010100000000
view.setInt8(1, -1); 0000010111111111

【定型数组是视图】

ES6定型数组实际上是用于数组缓冲区的特定类型的视图,可以强制使用特定的数据类型,而不是使用通用的DataView对象来操作数组缓冲区。8个特定类型的视图对应于8种数值型数据类型,uint8的值还有其他选择

“构造器名称”一列列举了几个定型数组的构造函数,其他列描述了每一个定型数组可包含的数据。Uint8ClampedArrayuint8Array大致相同,唯一的区别在于数组缓冲区中的值如果小于0或大于255,uint8ClampedArray会分别将其转换为0或255,例如,-1会变为0,300会变为255

定型数组操作只能在特定的数据类型上进行,例如,所有Int8Array的操作都使用int8类型的值。定型数组中元素的尺寸也取决于数组的类型,Int8Array中的元素占一个字节,而Float64Array中的每个元素占8字节。所幸的是,可以像正常数组一样通过数值型索引来访问元素,从而避免了调用DataViewsetget方法时的尴尬场面

【创建特定类型的视图】

定型数组构造函数可以接受多种类型的参数,所以可以通过多种方法来创建定型数组。首先,可以传入DataView构造函数可接受的参数来创建新的定型数组,分别是数组缓冲区、可选的比特偏移量、可选的长度值

let buffer = new ArrayBuffer(10),
view1 = new Int8Array(buffer),
view2 = new Int8Array(buffer, 5, 2);
console.log(view1.buffer === buffer); // true
console.log(view2.buffer === buffer); // true
console.log(view1.byteOffset); // 0
console.log(view2.byteOffset); // 5
console.log(view1.byteLength); // 10
console.log(view2.byteLength); // 2
let ints = new Int16Array(2),
floats = new Float32Array(5);
console.log(ints.byteLength); // 4
console.log(ints.length); // 2
console.log(floats.byteLength); // 20
console.log(floats.length); // 5

ints数组创建时含有两个空元素,每个16比特整型值需要两个字节,因而分配了4字节给该数组;floats数组创建时含有5个空元素,每个元素占4字节,所以共需要20字节。在这两种情况下,如果要访问新创建的缓冲区,则可以通过buffer属性来实现

[注意]调用定型数组的构造函数时如果不传参数,会按照传入0来处理,这样由于缓冲区没有分配到任何比特,因而创建的定型数组不能用来保存数据

1、一个定型数组

该数组中的每个元素会作为新的元素被复制到新的定型数组中。例如,如果将一个int8数组传入到Int16Array构造函数中,int8的值会被复制到一个新的int16数组中,新的定型数组使用新的数组缓冲区

2、一个可迭代对象

对象的迭代器会被调用,通过检索所有条目来选取插入到定型数组的元素,如果所有元素都是不适用于该视图类型的无效类型,构造函数将会抛出一个错误

3、一个数组

数组中的元素会被复制到一个新的定型数组中,如果所有元素都是不适用于该视图类型的无效类型,构造函数将会抛出一个错误

4、一个类数组对象

与传入数组的行为一致
在每个示例中,新创建的定型数组的数据均取自源对象,这在用一些值初始化定型数组时尤为有用

let ints1 = new Int16Array([25, 50]),
ints2 = new Int32Array(ints1);
console.log(ints1.buffer === ints2.buffer); // false
console.log(ints1.byteLength); // 4
console.log(ints1.length); // 2
console.log(ints1[0]); // 25
console.log(ints1[1]); // 50
console.log(ints2.byteLength); // 8
console.log(ints2.length); // 2
console.log(ints2[0]); // 25
console.log(ints2[1]); // 50

相同点

定型数组和普通数组有几个相似之处,在许多情况下可以按照普通数组的使用方式去使用定型数组。例如,通过length属性可以查看定型数组中含有的元素数量,通过数值型索引可以直接访问定型数组中的元素

let ints = new Int16Array([25, 50]);
console.log(ints.length); // 2
console.log(ints[0]); // 25
console.log(ints[1]); // 50
ints[0] = 1;
ints[1] = 2;
console.log(ints[0]); // 1
console.log(ints[1]); // 2

[注意]可以修改length属性来改变普通数组的大小,而定型数组的length属性是一个不可写属性,所以不能修改定型数组的大小,如果尝试修改这个值,在非严格模式下会直接忽略该操作,在严格模式下会抛出错误

【通用方法】

定型数组也包括许多在功能上与普通数组方法等效的方法,以下方法均可用于定型数组

copyWithin()
entries()
fill()
filter()
find()
findIndex()
forEach()
indexOf()
join()
keys()
lastIndexOf()
map()
reduce()
reduceRight()
reverse()
slice()
some()
sort()
values()
let ints = new Int16Array([25, 50]),
mapped = ints.map(v => v * 2);
console.log(mapped.length); // 2
console.log(mapped[0]); // 50
console.log(mapped[1]); // 100
console.log(mapped instanceof Int16Array); // true

【相同的迭代器】

定型数组与普通数组有3个相同的迭代器,分别是entries()方法、keys()方法和values()方法,这意味着可以把定型数组当作普通数组一样来使用展开运算符、for-of循环

let ints = new Int16Array([25, 50]),
intsArray = [...ints];
console.log(intsArray instanceof Array); // true
console.log(intsArray[0]); // 25
console.log(intsArray[1]); // 50

【of()方法和from()方法】

所有定型数组都含有静态of()方法和from()方法,运行效果分别与Array.of()方法和Array.from()方法相似,区别是定型数组的方法返回定型数组,而普通数组的方法返回普通数组

let ints = Int16Array.of(25, 50),
floats = Float32Array.from([1.5, 2.5]);
console.log(ints instanceof Int16Array); // true
console.log(floats instanceof Float32Array); // true
console.log(ints.length); // 2
console.log(ints[0]); // 25
console.log(ints[1]); // 50
console.log(floats.length); // 2
console.log(floats[0]); // 1.5
console.log(floats[1]); // 2.5

不同点

定型数组与普通数组最重要的差别是:定型数组不是普通数组。它不继承自Array,通过Array.isArray()方法检查定型数组返回的是false

let ints = new Int16Array([25, 50]);
console.log(ints instanceof Array); // false
console.log(Array.isArray(ints)); // false

【行为差异】

let ints = new Int16Array([25, 50]);
console.log(ints.length); // 2
console.log(ints[0]); // 25
console.log(ints[1]); // 50
ints[2] = 5;
console.log(ints.length); // 2
console.log(ints[2]); // undefined
let ints = new Int16Array(["hi"]);
console.log(ints.length); // 1
console.log(ints[0]); // 0
mapped = ints.map(v => "hi");
console.log(mapped.length); // 2
console.log(mapped[0]); // 0
console.log(mapped[1]); // 0
console.log(mapped instanceof Int16Array); // true
console.log(mapped instanceof Array); // false

【缺失的方法】

尽管定型数组包含许多与普通数组相同的方法,但也缺失了几个。以下方法在定型数组中不可使用

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

【附加方法】

定型数组中还有两个没出现在普通数组中的方法set()subarray()。这两个方法的功能相反,set()方法将其他数组复制到已有的定型数组,subarray()提取已有定型数组的一部分作为一个新的定型数组

set()方法接受两个参数:一个是数组(定型数组或普通数组都支持);一个是可选的偏移量,表示开始插入数据的位置,如果什么都不传,默认的偏移量为0。合法数据从作为参数传入的数组复制至目标定型数组中

let ints = new Int16Array(4);
ints.set([25, 50]);
ints.set([75, 100], 2);
console.log(ints.toString()); // 25,50,75,100
let ints = new Int16Array([25, 50, 75, 100]),
subints1 = ints.subarray(),
subints2 = ints.subarray(2),
subints3 = ints.subarray(1, 3);
console.log(subints1.toString()); // 25,50,75,100
console.log(subints2.toString()); // 75,100
console.log(subints3.toString()); // 50,75
上一篇 下一篇

猜你喜欢

热点阅读