介绍
ES6新增了定型数组,定型数组严格来说不是数组,因为isArray()对它们返回false。定型数组与常规数组存在以下几个区别。
- 定型数组的元素全部都是数值,并允许指定存储在数组中的数值的类型和大小。
- 创建定型数组时必须指定长度,且该长度不能再改变。
- 定型数组的元素在创建时始终都会被初始化为0。
定型数组实现了数组实现的大部分函数,待会会详细介绍。
定型数组的类型
JavaScript并未定义TypedArray类,而是定义了11种定型数组,每种都有自己的元素类型和构造函数。
| 构造函数 | 数值类型 |
|---|---|
| Int8Array() | 有符号字节 |
| Uint8Array() | 无符号字节 |
| Uint8ClampedArray() | 无符号字节(上溢不归零) |
| Int16Array() | 有符号16位短整数 |
| Uint16Array() | 无符号16位短整数 |
| Int32Array() | 有符号32位整数 |
| Uint32Array() | 无符号32位整数 |
| BigInt64Array() | 有符号64位BigInt值(ES2020) |
| BigUint64Array() | 无符号64位BigInt值(ES2020) |
| Float32Array() | 32位浮点值 |
| Float64Array() | 64位浮点值: 常规JavaScript值 |
Uint8ClampedArray()是Uint8Array的一种特殊变体,这两种类型都保存无符号字节,可表示的数值范围是0到255。对于Uint8Array来说,如果要存储的元素大于255或小于0,这个值会翻转为其他值,但Uint8ClampedArray()会固定为255或0,不会翻转(这对于HTML元素的低级api操作像素颜色是必须的)。
const nums = new Uint8Array(2);
nums[0] = 256;
nums[1] = -1;
console.log(nums);
// Uint8Array(2) [ 0, 255 ]
// *进行了翻转 11111111 -> 1(溢出) 00000000 、00000000 -> 11111111
const clampedNums = new Uint8ClampedArray(2);
clampedNums[0] = 256;
clampedNums[1] = -1;
console.log(clampedNums)
// Uint8ClampedArray(2) [ 255, 0 ]
// *不会翻转
每种定型数组的构造函数都有一个BYTES_PRE_ELEMENT属性,根据类型不同,这个属性的值可能为1(8)、2(16)、3(32)、4(64)。
创建定型数组
通过构造函数
创建定型数组最简单方式就是调用相应构造函数,并传入一个表示数组元素个数的数值参数
let bytes = new Uint8Array(1024); //1024位无符号字节
let matrix = new Float64Array(9); //9位64位浮点值定型数组
通过from()和of()方法
如果以这种方式初始化,数组元素一定会全部初始化为0、0.0。如果你想创建它们时指定值。可以使用每种定型数组构造函数都有的静态工厂方法form()和of(),类似于Array.from()和Array.of()。
const white = Uint8ClampedArray.of(255,255,255);
console.log(white)
// Uint8ClampedArray(3) [ 255, 255, 255 ]
from()方法期待一个类数组或可迭代对象作为其第一个参数。
const ints = Int8Array.from(white);
console.log(ints)
// Int8Array(3) [ -1, -1, -1 ]
// 同样三个数值,但是转化为了有符号数值。
但在通过已有数组、可迭代或类数组对象创建新定型数组时,为适应类型限制,已有的值可能被截短,这个过程不会有警告或报错
通过ArrayBuffer
ArrayBuffer是对一块内存的不透明引用,可以通过构造函数创建ArrayBuffer,传入想分配内存的字节数即可。
const buffer = new ArrayBuffer(1024 * 1024);
console.log(buffer.byteLength)
// 1048576 (1024 * 1024)
ArrayBuffer类不允许读取或写入分配的任何字节,但可以创建使用该缓存区内存的定型数组,通过这个数组来读取或者写入该内存。在调用定型数组的构造函数时需要将ArrayBuffer作为第一个参数,将该缓存区内的字节偏移量作为第二个参数,将数组的长度作为第三个参数。
- 第二、三个参数可省略,省略长度会默认为偏移起点到末尾,一起省略会从开头到结尾
- 数组的内存必须对齐,如果你指定了字节偏移量,那么这个值应该是类型大小的倍数,比如Int32Array要求必须是4的倍数,Float64Array要求必须是8的倍数。
const asbytes = new Uint8Array(buffer);
// 按字节查看
const asints = new Int32Array(buffer);
// 按32位有符号整数查看
const lastk = new Uint8Array(buffer, 1023*1024);
// 按字节查看最后1千字节
const ints2 = new Int32Array(buffer, 1024, 256);
// 按256位整数查看第二个1千字节
虽然前两个方法没有明确指定ArrayBuffer,但要知道,所有定向数组底层都有一个ArrayBuffer,如果没有明确指定,则会自动创建一块适当大小的缓存区,待会还会详细介绍。
使用定型数组
创建定型数组后,可以通过常规的中括号语法读取或写入元素。 此外,前面说过虽然定型数组不是真的数组,但他们实现了多数数组方法,因此几乎可以像常规数组一样使用它们
const a = new Uint8Array(2);
a[0] = 1;
a[1] = 2;
console.log(a); // Uint8Array(2) [ 1, 2 ]
const c = a.map((value) => value * value);
console.log(c); // Uint8Array(2) [ 1, 4 ]
定型数组的长度是固定的,因此length属性是只读的,而定型数组并未实现改变数组长度的方法(push()、pop()、unshift()、shift()、splice()等)
定型数组的方法与属性
除了标准的数组方法,定型数组还实现了他们自己的一些方法和属性
set()
set()方法用于一次性设置定型数组的多个元素,以一个数组或定型数组作为第一个参数,以元素偏移量作为其可选的第二个参数,不指定则默认为0。
const bytes = new Uint8Array(20); // 20长度的定型数组
const pattern = new Uint8Array([0, 1, 2, 3, ]); // 创建第二个定型数组
bytes.set(pattern); // 将pattern复制到bytes开头
bytes.set(pattern, 4); // 将pattern复制到bytes第四位开始
bytes.set([0, 1, 2, 3], 8); // 直接从一个常规数组复制
console.log(bytes.slice(0,12)); // 利用slice方法截取前12位
// Uint8Array(12) [0, 1, 2, 3, 0, 1, 2, 3, 0, 1, 2, 3]
subarray()
返回调用它的定型数组的一部分,与slice()接收相同的参数,而且行为也大致一样,区别在于slice会返回新的、独立的定型数组返回指定元素,而subarray不复制内存,只返回相同底层值的一个新视图
const ints = new Uint8Array([1, 2, 3, 4, 5]);
const a = ints.slice(1,3)
const b = ints.subarray(1,3)
console.log(a) // Uint8Array(2) [ 2, 3 ]
console.log(b) // Uint8Array(2) [ 2, 3 ]
ints.fill(6) // 修改ints值
console.log(a) // Uint8Array(2) [ 2, 3 ] 不会改变
console.log(b) // Uint8Array(2) [ 6, 6 ] 会根据原数据进行改变s
3个底层缓存区属性
- buffer:定型数组的ArrayBuffer属性。
- byteOffset: 定型数组数据在这个底层缓冲区的起点位置(偏移量)。
- byteLength: 定型数组的字节长度。
console.log(ints.buffer === b.buffer) // true 使用的同一块ArrayBuffer
console.log(ints.byteOffset) // 0 偏移量为0
console.log(b.byteOffset) // 1 偏移量为1
console.log(ints.byteLength) // 5 长度为5
console.log(b.byteLength) // 2 长度为2
console.log(b.buffer.byteLength) //5 但底层缓存区长度为5
对于任何定型数组a都有 a.length * a.BYTES_PRE_ELEMENT === a.bytelength
前面说过每给定型数组都会自动创建一个缓存区ArrayBuffer,现在知道了可以通过buffer属性获取到,与此同时,我们现在也可以先创建定型数组,再使用这个定型数组自动创建的ArrayBuffer缓冲区创建其他定型数组
const bytes = new Uint8Array(1024);
const ints = new Uint32Array(bytes.buffer)
// 通过bytes创建的缓存区创建一个256个整数的定型数组
const floats = new Float64Array(bytes.buffer);
// 或者128个双精度浮点值
最后,内容多参考自《JavaScript权威指南》(第七版),如有理解不正确的地方,欢迎指正讨论!