Array 的新方法

Array.of

在 JavaScript 中,有两种方法创建一个数组,一个是通过构造函数 Array,另一个是通过字面量 [] 的方式。通过 Array 创建一个数组时,如果传入了一个数字 $n$,表示的是创建一个容量为 $n$ 的数组;如果传入了一个参数,但是不是一个数字,那么会创建一个包含该参数的数组;如果传入多个参数,那么创建一个包含这些参数的数组

let arr = new Array(2); // 创建长度为 2 的数组
console.log(arr); // [ <2 empty items> ]

arr = new Array("2");
console.log(arr); // [ '2' ]

arr = new Array(1, 2, 3);
console.log(arr); // [ 1, 2, 3 ]

根据参数个数以及类型的不同,创建出的数组也不同,这种行为可能会为开发者带来记忆上的负担,所以在 ES6 中提供了一个 Array.of 方法,它可以接收多个参数,返回一个数组,数组中的元素就是传入的参数,与参数的个数以及类型无关

let arr = Array.of(2);
console.log(arr); // [ 2 ]

arr = Array.of(1, 2, 3);
console.log(arr); // [ 1, 2, 3 ]

Array.from

Array.from 方法的作用是将一个类数组转化为真正的数组,例如将 arguments 转化为真正的数组

function translateArray() {
let arr = Array.from(arguments);
console.log(arr instanceof Array);
}

translateArray(1, 2); // true

也可以将一个可迭代的对象转化为真正的数组

let obj = {
*[Symbol.iterator]() {
yield 1;
yield 2;
yield 3;
}
};

let arr = Array.from(obj);

console.log(arr); // [ 1, 2, 3 ]
console.log(arr instanceof Array); // true

Array.from 的第二个参数还可以接收一个回调函数,类似于 map 函数

function translateArray() {
return Array.from(arguments, item => item + 1);
}

let numbers = translateArray(1, 2);
console.log(numbers); // [ 2, 3 ]

如果在回调函数中需要用到 this 的话,也可以通过第三个参数传入 this。

find 和 findIndex

find 和 findIndex 都是用来搜索的方法,它们都接收一个回调函数作为条件来进行搜索

let arr = [1, 2, 3, 4, 5];

let item = arr.find(i => i % 2 === 0);
let index = arr.findIndex(i => i % 2 === 0);

// 第一个偶数
console.log(item); // 2
// 第一个偶数出现的下标
console.log(index); // 1

可以通过第二个参数传入回调函数中用到的 this。

fill

fill 方法的作用是将数组中的所有元素填充为指定值

let arr = new Array(5);
console.log(arr); // [ <5 empty items> ]

arr.fill(3);
console.log(arr); // [ 3, 3, 3, 3, 3 ]

fill 方法还可以接收起始位置和终止位置,表示将数组某个部分填充为指定值

arr.fill(5, 0, 3); // 将 [0, 3) 填充为 5
console.log(arr); // [ 5, 5, 5, 3, 3 ]

copyWithin

let arr = [1, 2, 3, 4, 5];

// 复制 [0, 3) 到以下标为 1 开始的位置
arr.copyWithin(1, 0, 3);
console.log(arr); // [ 1, 1, 2, 3, 5 ]

定型数组

在 JavaScript 中数字都是使用浮点数存储的,即使用 64 位来存储数字,但是在一些特殊的场合,例如对于图像,对于每一个像素只需要 8 位来存储即可,如果使用 64 位存储数据,必然会造成内存上的浪费,并且是 8 倍的浪费,所以就 ES6 就提出了定型数组来针对这些特殊的场景。

ArrayBuffer

所有定型数组的基础都是 ArrayBuffer,它表示占据一定字节的内存区域,通过调用 ArrayBuffer 构造函数,可以分配指定数目字节的内存

let buffer = new ArrayBuffer(10); // 分配 10 个字节的内存
console.log(buffer);

输出为

ArrayBuffer {
[Uint8Contents]: <00 00 00 00 00 00 00 00 00 00>,
byteLength: 10
}

DataView

我们可以通过 DataView 来查看和操作 ArrayBuffer

const buffer = new ArrayBuffer(10);
const view = new DataView(buffer);

DataView 构造函数接收一个 ArrayBuffer 对象,除此之外还可以接收两个可选参数

  • byteOffset:偏移量,默认是 0
  • byteLength:字节长度,默认是 buffer 的长度
// 获得偏移量为 0,长度为 2 字节的 view
const view = new DataView(buffer, 0, 2);

DataView 对象包含如下属性

  • buffer:传入构造函数的 ArrayBuffer 对象
  • byteOffset:传入构造函数的 byteOffset
  • byteLength:传入构造函数的 byteLength
const buffer = new ArrayBuffer(10);
const view1 = new DataView(buffer);
const view2 = new DataView(buffer, 2, 5);

console.log(view1.buffer === view2.buffer); // true

console.log(view1.byteOffset); // 0
console.log(view1.byteLength); // 10

console.log(view2.byteOffset); // 2
console.log(view2.byteLength); // 5

通过下面的方法可以读取和设置 buffer 中的值

  • getInt8(offset, littleEndian)
  • setInt8(offset, value, littleEndian)
  • getUInt8(offset, littleEndian)
  • setUInt8(offset, value, littleEndian)

以上暂且忽略参数 littleEndian

let buffer = new ArrayBuffer(10);
let view = new DataView(buffer);

// 0000 1010
view.setInt8(0, 10);
// 1000 1010 补码 1111 0110
view.setInt8(1, -10);

let s = view.getInt8(0);
console.log(s); // 10

// 1111 0110 => 246
s = view.getUint8(1);
console.log(s); // 246

此外还可以通过下面的方法设置浮点数的值

  • getFloat32
  • setFloat32
  • getFloat64
  • setFloat64

接收的参数同上面一样

// 3F 80 00 00
view.setFloat32(0, 1);
// 3F 80 00 00
view.setFloat32(2, 1);

// 3F 80 00 00 3F 80 00 00
res = view.getFloat64(0);
console.log(res); // 0.007933616638183594

定型数组

一个定型数组其实就是一个指定类型 DataView,具体有如下定型数组

https://cdn.jsdelivr.net/gh/LastKnightCoder/ImgHosting/20210118145653.png

向上图中的构造函数传入一个 buffer 创建定型数组,另外还可以传入 byteOffset 和 byteLength 两个可选参数,这两个可选参数的默认值同 DataView

let buffer = new ArrayBuffer(10);
let int8 = new Int8Array(buffer, 1, 2);

console.log(int8.buffer === buffer); // true
console.log(int8.byteOffset); // 1
console.log(int8.byteLength); // 2
console.log(int8); // Int8Array(2) [ 0, 0 ]

byteLength 必须能够整除定型数组表示的元素大小,假如向 Int16Array 传入一个大小为奇数的 byteLength,那么会报错,因为 Int16Array 中每个元素占据两个字节,而奇数不能整除 2,即无法平均分配,所以会报错

let int16 = new Int16Array(buffer, 0, 9); // 9 不能整除 2,会报错
https://cdn.jsdelivr.net/gh/LastKnightCoder/ImgHosting/20210118151811.png

除此以外,还可以直接传入一个数字,表示分配多少字节的内存给字节数组,同理需要注意传入的数字要能够整除定型数组中每个元素的大小

let int16 = new Int16Array(8);

如果没有向构造函数传入任何参数时,默认传入的参数为 0,这时定型数组不能保存数据,因为没有为它分配内存空间。

除此以外,定型数组的构造函数还可以接收如下几种类型的参数

  • 一个定型数组:将定型数组中的元素一个个的复制到新的定型数组中,所以两个定型数组的 buffer 是不同的
  • 一个可迭代的对象:调用对象的默认迭代器,将得到的元素一个个的添加到定型数组中
  • 一个数组
  • 一个类数组,如 arguments
let int8 = new Int8Array([25, 50]); // 使用数组作为参数
let int16 = new Int16Array(int8); // 使用定型数组作为参数
console.log(int8.buffer === int16.buffer); // false

定型数组与一般数组的相似之处

在很多的时候,可以把定型数组当做是一般数组

let int8 = new Int8Array([25, 50]);

console.log(int8.length); // 2
console.log(int8[0]); // 25
console.log(int8[1]); // 50

定型数组的长度一旦确定就不能再改变

共同方法

定型数组与普通数组有以下共同方法

https://cdn.jsdelivr.net/gh/LastKnightCoder/ImgHosting/20210118161532.png
let int8 = new Int8Array([25, 50]);

let newArr = int8.slice(0, 1);
console.log(newArr instanceof Int8Array); // true

let res = int8.find(i => i > 30);
console.log(res); // 50

共同的迭代器

定型数组也有三个迭代器,即 entries、keys 和 values,这意味着你可以使用展开运算符和 for … of 循环

let arr = [...int8];
console.log(arr instanceof Array); // true

for (let v of int8) {
console.log(v); // 25
// 50
}

of 与 from 方法

定型数组也有 of 和 from 方法

let int8 = Int8Array.of(3, 56);
let float32 = Float32Array.from([1.5, 2.5]);

console.log(int8); // Int8Array(2) [ 3, 56 ]
console.log(float32); // Float32Array(2) [ 1.5, 2.5 ]

定型数组与一般数组的不同之处

行为不同

一般数组的长度是可变的,但是定型数组的长度不可变

let int8 = new Int8Array([0, 1]);
console.log(int8.length); // 2

int8[2] = 5; // 无效,长度不可变
console.log(int8.length); // 2

定型数组会检查传入的数据是否有效,如果无效传入的值会被替换为 0

let int8 = new Int8Array(["hi"]); // 会被替换为 0
console.log(int8.length); // 1
console.log(int8[0]); // 0

对定型数组进行修改也会对值的类型进行检查,如果是无效的类型,则值会被替换为 0

let int8 = new Int8Array([1, 2]);
let newInt8 = int8.map(i => "hi");

console.log(newInt8); // Int8Array(2) [ 0, 0 ]

缺失的方法

定型数组没有以下方法

https://cdn.jsdelivr.net/gh/LastKnightCoder/ImgHosting/20210118163728.png

因为上面的方法都会改变数组的长度,所以定型数组没有以上方法是很容易理解的。

新增的方法

相比于一般数组,定型数组新增了两个方法

  • set:将一个数组中的元素复制到定型数组中
  • subarray:从定型数组中抽取一部分到新的定型数组中

set 方法接收一个数组,以及一个可选的参数,数据插入的偏移量,如果省略第二个参数,那么偏移量默认为 0

let int8 = new Int8Array(4); // 数组长度为 4
int8.set([1, 2]); // 将 [1, 2] 复制到定型数组前两个位置
int8.set([3, 4], 2); // 将 [3, 4] 复制到定型数组下标以 2 开始的两个位置

console.log(int8); // Int8Array(4) [ 1, 2, 3, 4 ]

subarray 接收两个可选的参数

  • start:起始位置,缺省为 0
  • end:终止位置,缺省为数组的长度

抽取的范围为 [start, end),左闭右开,返回一个新的定型数组

let int8 = new Int8Array([1, 2, 3, 4]);
let arr1 = int8.subarray(); // 缺省,默认是抽取整个数组
let arr2 = int8.subarray(1); // 抽取 [1, 4),4 为数组的长度
let arr3 = int8.subarray(1, 2); // 抽取 [1, 2)

console.log(arr1); // Int8Array(4) [ 1, 2, 3, 4 ]
console.log(arr2); // Int8Array(3) [ 2, 3, 4 ]
console.log(arr3); // Int8Array(1) [ 2 ]