`Array.prototype.flat` 与 `flatMap`:扁平化多维数组

Array.prototype.flat & flatMap:JavaScript 数组扁平化的双子星,以及如何避免“扁”过头! 🌟

各位观众老爷们,大家好!我是今天的主讲人,江湖人称“码农界吴彦祖”(好吧,我自己说的)。今天咱们要聊聊JavaScript数组里一对儿既实用又容易让人“扁”过头的兄弟——Array.prototype.flatflatMap

如果你曾经被多维数组搞得头昏眼花,想要把它们变成一维数组,方便操作,那么这对兄弟绝对是你的救星!但是,正所谓“水能载舟,亦能覆舟”,使用不当,它们也会让你的数据变得一塌糊涂。所以,请各位坐稳扶好,咱们这就开始今天的“扁平化之旅”!

一、什么?你还不知道什么是扁平化? 🤯

想象一下,你手里有一盒俄罗斯套娃,一层又一层,让人眼花缭乱。扁平化,就相当于把这盒套娃拆开,把所有的小娃娃都摆在桌面上,变成一个简单的队列。

在编程世界里,多维数组就像俄罗斯套娃一样,嵌套着一层又一层。而扁平化,就是把这些嵌套的数组“拆开”,变成一个一维数组。

举个例子:

const nestedArray = [1, [2, [3, [4, 5]]], 6];
// 我们想把它变成: [1, 2, 3, 4, 5, 6]

这就是扁平化的目标!

二、Array.prototype.flat:一“扁”惊人,简单粗暴! 💪

flat() 方法会创建一个新的数组,其中所有子数组元素都以递归的方式连接到该新数组中,直到达到指定深度。

1. 语法

array.flat([depth]);
  • depth (可选):指定要提取嵌套数组结构的深度。默认值为 1。 Infinity 可以用于完全扁平化所有嵌套层级的数组。

2. 用法示例

  • 默认深度:
const arr1 = [1, 2, [3, 4]];
console.log(arr1.flat()); // Output: [1, 2, 3, 4]

默认情况下,flat() 方法只会扁平化一层。

  • 指定深度:
const arr2 = [1, 2, [3, 4, [5, 6]]];
console.log(arr2.flat(2)); // Output: [1, 2, 3, 4, 5, 6]

这里我们指定深度为 2,所以两层嵌套都被扁平化了。

  • 无限深度:
const arr3 = [1, 2, [3, 4, [5, 6, [7, 8, [9, 10]]]]];
console.log(arr3.flat(Infinity)); // Output: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]

使用 Infinity,可以完全扁平化任何深度的嵌套数组,简直是懒人福音!

3. 注意事项

  • flat() 方法会移除数组中的空项。
const arr4 = [1, 2, , 4, 5]; // 注意中间的空项
console.log(arr4.flat()); // Output: [1, 2, 4, 5]

这个特性有时候很有用,可以帮你清理一些脏数据。

  • flat() 方法不会改变原数组。它会返回一个新的扁平化后的数组。
const arr5 = [1, [2, 3]];
const flattenedArr = arr5.flat();
console.log(arr5); // Output: [1, [2, 3]] (原数组不变)
console.log(flattenedArr); // Output: [1, 2, 3] (新数组)

记住这一点,不要试图直接修改原数组!

三、Array.prototype.flatMap:扁平化 + 映射,一步到位! 🚀

flatMap() 方法首先使用映射函数转换每个元素,然后将结果扁平化为一个新数组。它等同于 map() 后跟深度为 1 的 flat()

1. 语法

array.flatMap(callback(currentValue[, index[, array]])[, thisArg])
  • callback:为数组中的每个元素执行的函数,返回新数组的元素。
  • currentValue:当前正在处理的元素。
  • index (可选):当前元素的索引。
  • array (可选):调用 flatMap() 的数组。
  • thisArg (可选):执行 callback 时用作 this 的值。

2. 用法示例

  • 简单的映射和扁平化:
const arr6 = [1, 2, 3];
console.log(arr6.flatMap(x => [x * 2])); // Output: [2, 4, 6]

这里,我们首先使用 map() 的思想,将每个元素乘以 2,然后使用 flat() 扁平化,相当于 arr6.map(x => [x * 2]).flat()

  • 更复杂的场景:

假设你有一个字符串数组,你想把每个字符串分割成单词,并得到一个包含所有单词的数组:

const sentences = ["hello world", "this is a test"];
const words = sentences.flatMap(sentence => sentence.split(" "));
console.log(words); // Output: ["hello", "world", "this", "is", "a", "test"]

是不是很方便?一行代码搞定!

3. 注意事项

  • flatMap() 的深度固定为 1。这意味着它只会扁平化一层。如果你需要更深层次的扁平化,你需要结合 flat() 方法。

  • flatMap() 方法同样不会改变原数组,而是返回一个新的数组。

四、flat vs flatMap:选择困难症?不存在的! 🤝

既然有了 flat(),为什么还要有 flatMap() 呢?它们有什么区别?什么时候该用哪个呢?

特性 flat() flatMap()
功能 扁平化数组 映射 + 扁平化 (深度为 1)
深度 可指定深度,默认为 1,可使用 Infinity 固定为 1
使用场景 纯粹的扁平化操作 需要先映射再扁平化,且只需要扁平化一层的情况
代码简洁性 相对简单 可以将 map()flat() 组合操作简化为一行代码

总的来说,如果你只需要扁平化数组,flat() 是你的首选。如果你需要在扁平化之前对数组元素进行一些转换,并且只需要扁平化一层,那么 flatMap() 更加方便快捷。

举个例子:

  • 场景 1:纯粹的扁平化
const deeplyNestedArray = [1, [2, [3, 4]]];
const flattenedArray = deeplyNestedArray.flat(Infinity); // 使用 flat()
console.log(flattenedArray); // Output: [1, 2, 3, 4]
  • 场景 2:映射 + 扁平化
const numbers = [1, 2, 3];
const doubledNumbers = numbers.flatMap(number => [number * 2]); // 使用 flatMap()
console.log(doubledNumbers); // Output: [2, 4, 6]

五、实战演练:避免“扁”过头! ⚠️

虽然 flat()flatMap() 很强大,但是也要小心使用,避免“扁”过头,导致数据丢失或者逻辑错误。

1. 数据结构错误:

假设你有一个包含用户信息的数组,每个用户信息包含一个 hobbies 数组:

const users = [
  { id: 1, name: "Alice", hobbies: ["reading", "coding"] },
  { id: 2, name: "Bob", hobbies: ["gaming", "music"] }
];

如果你想获取所有用户的爱好,你可能会这样写:

const allHobbies = users.flatMap(user => user.hobbies);
console.log(allHobbies); // Output: ["reading", "coding", "gaming", "music"]

这样做看起来没问题,但是如果你需要根据爱好查找用户,就比较麻烦了,因为你丢失了用户和爱好之间的关联。

正确的做法是保留用户和爱好之间的关联:

const userHobbies = users.map(user => ({ userId: user.id, hobbies: user.hobbies }));
console.log(userHobbies);
// Output:
// [
//   { userId: 1, hobbies: ["reading", "coding"] },
//   { userId: 2, hobbies: ["gaming", "music"] }
// ]

或者,如果你确实需要一个包含所有爱好的数组,并且还需要知道每个爱好属于哪个用户,你可以这样做:

const allHobbiesWithUser = users.flatMap(user => user.hobbies.map(hobby => ({ userId: user.id, hobby })));
console.log(allHobbiesWithUser);
// Output:
// [
//   { userId: 1, hobby: "reading" },
//   { userId: 1, hobby: "coding" },
//   { userId: 2, hobby: "gaming" },
//   { userId: 2, hobby: "music" }
// ]

2. 逻辑错误:

假设你有一个包含商品信息的数组,每个商品信息包含一个 variants 数组,表示商品的各种规格:

const products = [
  { id: 1, name: "T-shirt", variants: [{ size: "S", color: "red" }, { size: "M", color: "blue" }] },
  { id: 2, name: "Pants", variants: [{ size: "L", color: "black" }, { size: "XL", color: "gray" }] }
];

如果你想获取所有商品的规格,你可能会这样写:

const allVariants = products.flatMap(product => product.variants);
console.log(allVariants);
// Output:
// [
//   { size: "S", color: "red" },
//   { size: "M", color: "blue" },
//   { size: "L", color: "black" },
//   { size: "XL", color: "gray" }
// ]

这样做看起来没问题,但是如果你需要根据规格查找商品,就比较麻烦了,因为你丢失了商品和规格之间的关联。

正确的做法是保留商品和规格之间的关联:

const productVariants = products.map(product => ({ productId: product.id, variants: product.variants }));
console.log(productVariants);
// Output:
// [
//   { productId: 1, variants: [{ size: "S", color: "red" }, { size: "M", color: "blue" }] },
//   { productId: 2, variants: [{ size: "L", color: "black" }, { size: "XL", color: "gray" }] }
// ]

或者,如果你确实需要一个包含所有规格的数组,并且还需要知道每个规格属于哪个商品,你可以这样做:

const allVariantsWithProduct = products.flatMap(product => product.variants.map(variant => ({ productId: product.id, variant })));
console.log(allVariantsWithProduct);
// Output:
// [
//   { productId: 1, variant: { size: "S", color: "red" } },
//   { productId: 1, variant: { size: "M", color: "blue" } },
//   { productId: 2, variant: { size: "L", color: "black" } },
//   { productId: 2, variant: { size: "XL", color: "gray" } }
// ]

记住,扁平化的目的是为了方便操作,而不是为了简化数据结构,导致信息丢失。在使用 flat()flatMap() 之前,一定要仔细思考你的数据结构和业务逻辑,确保不会“扁”过头!

六、兼容性:老旧浏览器怎么办? 👴👵

Array.prototype.flatflatMap 是 ES2019 (ES10) 引入的特性。这意味着在一些老旧的浏览器中可能不支持。

如果你需要兼容这些老旧浏览器,可以使用 polyfill。polyfill 是一段代码,用于提供老旧浏览器不支持的新特性。

以下是一个简单的 flat() polyfill:

if (!Array.prototype.flat) {
  Array.prototype.flat = function(depth = 1) {
    return this.reduce(function(acc, val) {
      return acc.concat(Array.isArray(val) && depth > 0 ? val.flat(depth - 1) : val);
    }, []);
  };
}

以下是一个简单的 flatMap() polyfill:

if (!Array.prototype.flatMap) {
  Array.prototype.flatMap = function(callback, thisArg) {
    return this.map(callback, thisArg).flat();
  };
}

当然,你也可以使用一些成熟的 polyfill 库,例如 core-js

七、总结:扁平化,让你的代码更优雅! 💃

Array.prototype.flatflatMap 是 JavaScript 数组扁平化的利器。它们可以帮助你简化代码,提高效率。但是,也要注意使用方法,避免“扁”过头,导致数据丢失或者逻辑错误。

希望今天的分享对你有所帮助!记住,编程的乐趣在于不断学习和探索! 祝大家编码愉快,bug 远离! 🚀🎉

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注