Array.prototype.flatMap()

Baseline Widely available

This feature is well established and works across many devices and browser versions. It’s been available across browsers since January 2020.

flatMap() 方法对数组中的每个元素应用给定的回调函数,然后将结果展开一级,返回一个新数组。它等价于在调用 map() 方法后再调用深度为 1 的 flat() 方法(arr.map(...args).flat()),但比分别调用这两个方法稍微更高效一些。

尝试一下

语法

js
flatMap(callbackFn)
flatMap(callbackFn, thisArg)

参数

callbackFn

一个在数组的每个元素上执行的函数。它应该返回一个包含新数组元素的数组,或是要添加到新数组中的单个非数组值。该函数将被传入以下参数:

element

数组中正在处理的当前元素。

index

数组中正在处理的当前元素的索引。

array

调用 flatMap() 的当前数组。

thisArg 可选

在执行 callbackFn 时用作 this 的值。参见迭代方法

返回值

一个新的数组,其中每个元素都是回调函数的结果,并且被展开一级。

描述

flatMap() 方法是一个迭代方法。有关回调函数的详细说明,请参见 Array.prototype.map()flatMap() 方法等同于调用 map(callbackFn, thisArg) 后再调用 flat(1)——对于每个元素,它都会生成一个新元素数组,并将生成的数组连接起来形成一个新数组。

flatMap() 方法是通用的。它只需要 this 值具有 length 属性和整数键属性即可。但是,如果要展开从回调函数 callbackFn 返回的值,则该值必须是数组。

替代方案

预分配数组并显式迭代

js
const arr = [1, 2, 3, 4];

arr.flatMap((x) => [x, x * 2]);
// 等价于
const n = arr.length;
const acc = new Array(n * 2);
for (let i = 0; i < n; i++) {
  const x = arr[i];
  acc[i * 2] = x;
  acc[i * 2 + 1] = x * 2;
}
// [1, 2, 2, 4, 3, 6, 4, 8]

需要注意的是,在这种特定情况下,flatMap 方法的性能比 for 循环要慢——因为需要创建临时数组并进行垃圾回收,同时返回的数组不需要经常调整大小。但是,在注重灵活性和可读性的情况下,flatMap 仍可能是正确的解决方案。

示例

map() 与 flatMap()

js
const arr1 = [1, 2, 3, 4];

arr1.map((x) => [x * 2]);
// [[2], [4], [6], [8]]

arr1.flatMap((x) => [x * 2]);
// [2, 4, 6, 8]

// 只有一层被展平
arr1.flatMap((x) => [[x * 2]]);
// [[2], [4], [6], [8]]

虽然上面的示例可以通过使用 map() 方法自行实现,但下面的示例更好地展示了 flatMap() 方法的用法。

让我们从一组句子中生成单词列表。

js
const arr1 = ["it's Sunny in", "", "California"];

arr1.map((x) => x.split(" "));
// [["it's","Sunny","in"],[""],["California"]]

arr1.flatMap((x) => x.split(" "));
// ["it's","Sunny","in", "", "California"]

请注意,输出列表长度可能与输入列表长度不同。

map() 方法过程中添加和删除元素

flatMap 方法可以用作在 map 方法中添加和删除元素(修改元素数量)的方法。换句话说,它允许你将一些项处理为另一些项(通过单独处理每个输入项),而不总是一对一的。在这个意义上,它的作用类似于 filter 的相反操作。如果要保留该项,则返回一个包含该项的单元素数组,如果要添加元素,则返回一个包含多个元素的数组,如果要删除该项,则返回一个空数组。

js
// 假设我们想要删除所有负数,并将奇数拆分成偶数和 1
const a = [5, 4, -3, 20, 17, -33, -4, 18];
//         |\  \  x   |  | \   x   x   |
//        [4,1, 4,   20, 16, 1,       18]

const result = a.flatMap((n) => {
  if (n < 0) {
    return [];
  }
  return n % 2 === 0 ? [n] : [n - 1, 1];
});
console.log(result); // [4, 1, 4, 20, 16, 1, 18]

在稀疏数组上使用 flatMap()

callbackFn 不会被源数组中的空槽调用,因为 map() 不会调用,而 flat() 将忽略返回数组中的空槽。

js
console.log([1, 2, , 4, 5].flatMap((x) => [x, x * 2])); // [1, 2, 2, 4, 4, 8, 5, 10]
console.log([1, 2, 3, 4].flatMap((x) => [, x * 2])); // [2, 4, 6, 8]

在非数组对象上调用 flatMap()

flatMap() 方法读取 thislength 属性,然后访问每个整数索引。如果回调函数的返回值不是数组,则始终直接将其附加到结果数组的末尾。

js
const arrayLike = {
  length: 3,
  0: 1,
  1: 2,
  2: 3,
};
console.log(Array.prototype.flatMap.call(arrayLike, (x) => [x, x * 2]));
// [1, 2, 2, 4, 3, 6]

// 回调函数返回的类数组对象不会被展平
console.log(
  Array.prototype.flatMap.call(arrayLike, (x) => ({
    length: 1,
    0: x,
  })),
);
// [ { '0': 1, length: 1 }, { '0': 2, length: 1 }, { '0': 3, length: 1 } ]

规范

Specification
ECMAScript Language Specification
# sec-array.prototype.flatmap

浏览器兼容性

BCD tables only load in the browser

参见