Array.prototype.flat & flatMap:JavaScript 数组扁平化的双子星,以及如何避免“扁”过头! 🌟
各位观众老爷们,大家好!我是今天的主讲人,江湖人称“码农界吴彦祖”(好吧,我自己说的)。今天咱们要聊聊JavaScript数组里一对儿既实用又容易让人“扁”过头的兄弟——Array.prototype.flat
和 flatMap
。
如果你曾经被多维数组搞得头昏眼花,想要把它们变成一维数组,方便操作,那么这对兄弟绝对是你的救星!但是,正所谓“水能载舟,亦能覆舟”,使用不当,它们也会让你的数据变得一塌糊涂。所以,请各位坐稳扶好,咱们这就开始今天的“扁平化之旅”!
一、什么?你还不知道什么是扁平化? 🤯
想象一下,你手里有一盒俄罗斯套娃,一层又一层,让人眼花缭乱。扁平化,就相当于把这盒套娃拆开,把所有的小娃娃都摆在桌面上,变成一个简单的队列。
在编程世界里,多维数组就像俄罗斯套娃一样,嵌套着一层又一层。而扁平化,就是把这些嵌套的数组“拆开”,变成一个一维数组。
举个例子:
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.flat
和 flatMap
是 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.flat
和 flatMap
是 JavaScript 数组扁平化的利器。它们可以帮助你简化代码,提高效率。但是,也要注意使用方法,避免“扁”过头,导致数据丢失或者逻辑错误。
希望今天的分享对你有所帮助!记住,编程的乐趣在于不断学习和探索! 祝大家编码愉快,bug 远离! 🚀🎉