Array.from的5个小技巧

454 阅读5分钟

翻译:道奇
作者:Dmitri Pavlutin
原文:5 Handy Applications of JavaScript Array.from()

任何编程语言都有一些超出基础应用的函数,这归因于成功的设计和它试图解决广泛领域的问题。

JavaScript中也有这种函数,Array.from()就是其中一个:允许在JavaScript集合上进行大量有用的转换(数组,类数组的对象,直接量(iterable)如字符串、mapset等)。

本文会介绍5种Array.from()比较有用和有趣的使用案例。

1. 快速简介

在开始前,回顾一下Array.from()是做什么的。下面代码展示了如何调用这个函数:

Array.from(arrayLikeOrIterable[, mapFunction[, thisArg]]);

第一个必填参数arrayLikeOrIterable是个类数组对象或是直接量iterable,第二个可选参数mapFunction(item, index) {...}是集合中每个项都会调用的函数,返回值会插入到新的集合中。 最后,第三个可选参数thisArg是在mapFunction被调用时作为this值使用的。

例如,给一个类数组对象的数值做乘2运算:

const someNumbers = { '0': 10, '1': 15, length: 2 };

Array.from(someNumbers, value => value * 2); // => [20, 30]

2. 将类数组转换成数组

第一个有用的技巧就是Array.from()本身的直接应用:将类数组转换成数组。

一般情况下,这个奇怪的生物 “类数组对象” 是以参数的形式存在函数内部的,或者和DOM集合结合起来进行处理。

下面这个例子是对函数的参数进行求和:

function sumArguments() {
  return Array.from(arguments).reduce((sum, num) => sum + num);
}

sumArguments(1, 2, 3); // => 6

Array.from(arguments)将类数组arguments转换成一个数组。新数组是参数元素的和。

此外,可以将Array.from()应用在任何对象上,任何实现了iterable协议的原始对象,下面看几个例子:

Array.from('Hey');                   // => ['H', 'e', 'y']
Array.from(new Set(['one', 'two'])); // => ['one', 'two']

const map = new Map();
map.set('one', 1)
map.set('two', 2);
Array.from(map); // => [['one', 1], ['two', 2]]

3. 克隆数组

在JavaScript中有大量克隆数组的方法。

正如所料,Array.from()可以轻松浅拷贝一个数组:

const numbers = [3, 6, 9];
const numbersCopy = Array.from(numbers);

numbers === numbersCopy; // => false

Array.from(numbers)创建了numbers数组的浅拷贝副本。numbers === numbersCopy这个等式等于false,意味着就算有相同的子项,但它们还是不同的数组对象。

有没有可能使用Array.from()创建一份包括数组所有嵌套的备份?看下面的回答:

function recursiveClone(val) {
  return Array.isArray(val) ? Array.from(val, recursiveClone) : val;
}

const numbers = [[0, 1, 2], ['one', 'two', 'three']];
const numbersClone = recursiveClone(numbers);

numbersClone; // => [[0, 1, 2], ['one', 'two', 'three']]
numbers[0] === numbersClone[0] // => false

recursiveClone()创建了一份给定数组的深拷贝,这是通过在子项也是数组时循环调用recursiveClone()实现的。

4. 给数组填充值

如果需要给数组填充相同值,也可以使用Array.from()

定义一个函数,它创建了具有相同默认值的数组:

const length = 3;
const init   = 0;
const result = Array.from({ length }, () => init);

result; // => [0, 0, 0]

result数组有3个项,它们全部初始化为0。通过调用Array.from()时传入类数组对象{length}和返回初始化值的map函数来完成的。

另外也可以使用array.fill()达到同样的效果:

const length = 3;
const init   = 0;
const result = Array(length).fill(init);

result; // => [0, 0, 0]

fill()方法正确地向数组填充初始化值,并且会全部填满不会有空槽。

4.1 向数组填充多个新对象

如果初始化数组的每个项都是新的对象,使用Array.from()会更好:

const length = 3;
const resultA = Array.from({ length }, () => ({}));
const resultB = Array(length).fill({});

resultA; // => [{}, {}, {}]
resultB; // => [{}, {}, {}]

resultA[0] === resultA[1]; // => false
resultB[0] === resultB[1]; // => true

Array.from()创建的resultA,它被初始化成不同的空实例对象{},这是因为映射函数() => ({})每次调用时返回一个新对象。

fill()创建的resultB方法是初始化成同一个空对象实例。

4.2 array.map()

array.map()能得到同样的结果吗?下面就试一下:

const length = 3;
const init   = 0;
const result = Array(length).map(() => init);

result; // => [undefined, undefined, undefined]

map()看起来不正确,预期的是三个0的数组,结果是3个空槽。

这是因为Array(length)创建了3个空槽的数组,但是map()执行的时候跳过了这些空槽。

5. 生成范围内的数值

可以使用Array.from()生成在一个范围内的数值。例如,下面的range函数生成一个数组,其中的项从0开始,直到结束- 1:

function range(end) {
  return Array.from({ length: end }, (_, index) => index);
}

range(4); // => [0, 1, 2, 3]

range()函数内部,Array.from()提供了类数组{ length: end },映射函数就简单的返回当前的索引,这种方式可以生成范围内的值。

6. 数组项去重

Array.from()可以接受直接量类型(iterable)对象,这使得它可以快速移除数组中的重复项,这种操作可以通过set数据结构实现:

unction unique(array) {
  return Array.from(new Set(array));
}

unique([1, 1, 2, 3, 3]); // => [1, 2, 3]

首先,new Set(array)创建了一个包含所有数组项的集合,set内部会自动移除重复项。

因为set是直接量类型(iterable)的,Array.from()将唯一项抽到新的数组上。

7. 总结

Array.from()静态方法接受类数组对象或直接量类型(iterable)对象,还有一个映射函数。此外,该函数不会跳过空的直接量类型(iterable)对象。这些特性的组合给了Array.from()很多可能性。

正如上面介绍的,可以很简单的将类数组对象转换成数组,克隆数组,为数组填充初始值,生成范围内的值,移除重复项。

确实,Array.from()是良好的设计和灵活性的结合,允许广泛集合的转换。