在 JavaScript 數組處理中,我們經常需要先對每個元素進行轉換(映射),再將結果 “鋪平”(扁平化)。比如將數組中的每個字符串按空格拆分,然后合并成一個新數組。傳統做法是先用map()
轉換,再用flat()
扁平化,但這樣會創建中間數組,影響效率。ES2019 引入的flatMap()
方法,就像一套 “組合拳”,將映射和扁平化兩步操作合并為一,既簡潔又高效。今天,我們就來解鎖這個提升數組處理效率的實用方法。
一、認識 flatMap ():映射與扁平化的 “二合一” 工具
flatMap()
是數組原型上的方法,它的作用可以概括為:先對數組中的每個元素執行映射操作(類似map()
),再對結果執行一層扁平化(類似flat(1)
)。簡單說,flatMap()
等價于map()
followed by flat(1)
,但性能更優。
1.1 與 map () + flat () 的對比
// 原始數組
const sentences = ["Hello world", "I love JavaScript", "flatMap is useful"];// 方法1:map() + flat()
const words1 = sentences.map((sentence) => sentence.split(" ")) // 先映射:拆分成子數組.flat(); // 再扁平化:合并子數組// 方法2:flatMap()
const words2 = sentences.flatMap((sentence) => sentence.split(" "));
console.log(words1); // ["Hello", "world", "I", "love", "JavaScript", "flatMap", "is", "useful"]
console.log(words2); // 同上,結果完全一致
兩者結果相同,但flatMap()
只遍歷一次數組,且不創建中間數組(map()
的結果),因此在處理大型數組時性能更優。
1.2 基礎語法:簡潔的回調函數
flatMap()
的語法與map()
類似,接收一個回調函數和可選的this
指向:
array.flatMap(callback(element[, index[, array]])[, thisArg])
-
callback
:對每個元素執行的函數,返回一個數組(或其他可迭代對象),該數組會被扁平化一層。 -
thisArg
:執行callback
時的this
值。 -
返回值:經過映射和扁平化后的新數組。
示例:將數組元素翻倍并拆分
const numbers = [1, 2, 3];// 映射:每個元素翻倍后放在數組中;扁平化:合并子數組
const result = numbers.flatMap((num) => [num * 2, num * 3]);
console.log(result); // [2, 3, 4, 6, 6, 9]
回調函數返回[num * 2, num * 3]
,flatMap()
會將這些子數組合并成一個數組。
二、核心特性:只扁平化一層的 “精準控制”
flatMap()
的扁平化能力是有限的 —— 它只會將映射結果扁平化一層,不會遞歸處理深層嵌套的數組。這一點與flat(depth)
不同(flat()
可指定深度),也是flatMap()
的關鍵特性。
2.1 與 flat () 不同的扁平化深度
const arr = [1, [2, [3]], 4];// flatMap():只扁平化一層
const result1 = arr.flatMap((item) => {// 映射:原樣返回元素(模擬不映射,只看扁平化效果)return item;
});// flat(1):同樣扁平化一層
const result2 = arr.flat(1);// flat(2):扁平化兩層
const result3 = arr.flat(2);console.log(result1); // [1, 2, [3], 4](僅扁平一層)
console.log(result2); // [1, 2, [3], 4](與flatMap結果一致)
console.log(result3); // [1, 2, 3, 4](深層扁平)
可以看到,flatMap()
的扁平化效果等價于flat(1)
,無法處理深層嵌套。如果需要深層扁平化,仍需在flatMap()
之后調用flat(depth)
。
2.2 過濾元素的 “小技巧”
利用flatMap()
只扁平化一層的特性,我們可以在映射時返回空數組[]
,實現 “過濾” 效果(空數組會被扁平化為空,相當于刪除元素):
const numbers = [1, 2, 3, 4, 5, 6];// 保留偶數,過濾奇數(奇數返回空數組)
const evenNumbers = numbers.flatMap((num) => {return num % 2 === 0 ? [num] : [];
});console.log(evenNumbers); // [2, 4, 6]
這比filter()
+ map()
的組合更簡潔(numbers.filter(num => num % 2 === 0).map(num => num)
),且只遍歷一次數組。
三、實戰場景:flatMap () 的高效應用
flatMap()
在需要同時進行映射和扁平化的場景中表現出色,以下是幾個典型案例:
3.1 拆分字符串并去重
處理包含多個標簽的字符串數組,拆分后去重:
const tagGroups = ["html,css", "javascript,html", "css,react"];// 拆分所有標簽并去重
const uniqueTags = [...new Set(tagGroups.flatMap((group) => group.split(",")))];console.log(uniqueTags); // ["html", "css", "javascript", "react"]
-
flatMap()
先將每個字符串按,
拆分,再合并成一個標簽數組。 -
用
Set
去重后轉回數組,實現高效處理。
3.2 生成新的對象數組
將用戶數組轉換為 “用戶名 - 郵箱” 對數組:
const users = [{ name: "Alice", emails: ["alice@a.com", "alice.work@a.com"] },{ name: "Bob", emails: ["bob@b.com"] },
];// 生成 [{ name: "Alice", email: "alice@a.com" }, ...]
const userEmails = users.flatMap((user) =>user.emails.map((email) => ({ name: user.name, email: email }))
);console.log(userEmails);/*
[{ name: "Alice", email: "alice@a.com" },{ name: "Alice", email: "alice.work@a.com" },{ name: "Bob", email: "bob@b.com" }
]
*/
flatMap()
先對每個用戶映射出包含多個郵箱對象的數組,再合并成一個數組,避免了map()
后額外的flat()
操作。
3.3 處理樹形結構數據
將嵌套的評論數據 “鋪平” 為一維數組:
const comments = [{id: 1,text: "第一條評論",replies: [{ id: 11, text: "回復1" },{ id: 12, text: "回復2" },],},{id: 2,text: "第二條評論",replies: [],},
];// 提取所有評論(包括回復)的id和text
const allComments = comments.flatMap((comment) => [// 先包含當前評論{ id: comment.id, text: comment.text },// 再包含所有回復(會被扁平化)...comment.replies.map((reply) => ({ id: reply.id, text: reply.text })),
]);console.log(allComments);/*
[{ id: 1, text: "第一條評論" },{ id: 11, text: "回復1" },{ id: 12, text: "回復2" },{ id: 2, text: "第二條評論" }
]
*/
通過flatMap()
將嵌套的回復與主評論合并,一步完成映射和扁平化。
3.4 數據轉換與過濾結合
處理訂單數據,提取有效商品并轉換格式:
const orders = [{ id: 1, products: ["apple", "banana"], valid: true },{ id: 2, products: ["orange"], valid: false }, // 無效訂單{ id: 3, products: ["grape", "mango"], valid: true },
];// 提取有效訂單的商品,格式化為 { orderId, product }
const validProducts = orders.flatMap((order) => {// 過濾無效訂單(返回空數組)if (!order.valid) return [];// 映射有效商品return order.products.map((product) => ({orderId: order.id,product: product,}));
});console.log(validProducts);/*
[{ orderId: 1, product: "apple" },{ orderId: 1, product: "banana" },{ orderId: 3, product: "grape" },{ orderId: 3, product: "mango" }
]
*/
一次遍歷完成過濾和轉換,效率高于filter()
+ map()
+ flat()
的組合。
四、避坑指南:使用 flatMap () 的注意事項
4.1 不要期望深層扁平化
flatMap()
只能扁平化一層,對于深層嵌套的數組,需要額外處理:
const deepArray = [1, [2, [3, [4]]]];// 錯誤:flatMap()無法深層扁平化
const result1 = deepArray.flatMap((item) => item);
console.log(result1); // [1, 2, [3, [4]]](僅扁平一層)// 正確:先flatMap()再flat()
const result2 = deepArray.flatMap((item) => item).flat(2);
console.log(result2); // [1, 2, 3, 4]
4.2 回調函數需返回可迭代對象
flatMap()
的回調函數應返回數組或其他可迭代對象(如字符串、Set
等),否則會將返回值包裝成數組再扁平化:
const numbers = [1, 2, 3];// 回調返回非數組(數字)
const result = numbers.flatMap((num) => num * 2);
console.log(result); // [2, 4, 6]// 等價于:[ [2], [4], [6] ].flat() → [2,4,6]
雖然返回非數組也能工作,但建議始終返回數組,明確意圖。
4.3 性能考量:大型數組的處理
flatMap()
比map()
+ flat()
高效,但在處理超大型數組時,仍需注意:
-
避免在回調函數中執行復雜操作(會增加單次迭代耗時)。
-
如需多次處理,考慮分批次處理(避免阻塞主線程)。
4.4 瀏覽器兼容性
flatMap()
兼容所有現代瀏覽器(Chrome 69+、Firefox 62+、Safari 12+、Edge 79+),但 IE 完全不支持。如需兼容舊瀏覽器,可使用 Polyfill:
// flatMap()的簡易Polyfill
if (!Array.prototype.flatMap) {Array.prototype.flatMap = function (callback, thisArg) {return this.map(callback, thisArg).flat(1);};
}
五、總結
flatMap()
作為map()
和flat(1)
的組合,雖然功能看似簡單,卻在數組處理中有著不可替代的價值:
-
簡潔高效:一行代碼完成映射和扁平化,減少中間數組創建。
-
靈活多用:既能轉換數據,又能過濾元素,還能處理嵌套結構。
-
性能更優:比
map()
+flat()
少一次遍歷,適合大型數組。
在實際開發中,當你需要對數組元素進行轉換并希望結果是一維數組時,flatMap()
往往是最佳選擇。無論是處理字符串拆分、對象轉換,還是嵌套數據鋪平,它都能讓代碼更簡潔、高效。
記住:好的工具能讓復雜問題變簡單,flatMap()
就是這樣一個 “四兩撥千斤” 的數組方法。下次處理數組時,不妨想想:這個場景能用flatMap()
嗎?
你在項目中用過flatMap()
解決什么問題?歡迎在評論區分享你的經驗~