把多層巢狀陣列變成一維陣列是常見需求。記錄幾種實現方式,並對比適用場景。
場景
javascript
const nested = [1, [2, 3], [4, [5, 6]], [7, [8, [9]]]];
// 目標:[1, 2, 3, 4, 5, 6, 7, 8, 9]
方法一:遞迴
javascript
function flatten(arr) {
return arr.reduce((flat, item) => {
return flat.concat(Array.isArray(item) ? flatten(item) : item);
}, []);
}
flatten([1, [2, [3, [4]]]]); // [1, 2, 3, 4]
方法二:展開運算子迴圈
javascript
function flatten(arr) {
while (arr.some((item) => Array.isArray(item))) {
arr = [].concat(...arr);
}
return arr;
}
每次迴圈展開一層,直到沒有巢狀為止。
方法三:toString(限整數)
javascript
[1, [2, [3, [4]]]].toString().split(",").map(Number);
// [1, 2, 3, 4]
只適合純整數陣列,有侷限性。
方法四:Array.flat(ES2019)
javascript
// 展開一層
[1, [2, [3]]]
.flat() // [1, 2, [3]]
[
// 展開兩層
(1, [2, [3, [4]]])
].flat(2) // [1, 2, 3, [4]]
[
// 無限層展開
(1, [2, [3, [4]]])
].flat(Infinity); // [1, 2, 3, 4]
最簡潔,2018 年底 Chrome 69+ 支援,需要 polyfill。
指定深度展開
javascript
function flattenDepth(arr, depth = 1) {
if (depth === 0) return arr.slice();
return arr.reduce((flat, item) => {
if (Array.isArray(item) && depth > 0) {
return flat.concat(flattenDepth(item, depth - 1));
}
return flat.concat(item);
}, []);
}
flattenDepth([1, [2, [3, [4]]]], 2); // [1, 2, 3, [4]]
實際專案場景
javascript
// 場景:樹形選單資料,獲取所有葉子節點
const menuTree = [
{ id: 1, name: "首頁" },
{
id: 2,
name: "使用者管理",
children: [
{ id: 3, name: "使用者列表" },
{ id: 4, name: "角色管理" },
],
},
];
// 獲取所有選單項(包括父級)
function flattenMenu(menus) {
return menus.reduce((flat, menu) => {
if (menu.children) {
return flat.concat(menu, flattenMenu(menu.children));
}
return flat.concat(menu);
}, []);
}
flattenMenu(menuTree);
// [{ id: 1 }, { id: 2 }, { id: 3 }, { id: 4 }]
效能對比
對於大陣列,遞迴方式最快;Array.flat 內部也是最佳化的;toString 方式建立字串成本高。
小結
- 日常開發:
flat(Infinity)最簡潔(確認目標環境支援) - 需要相容舊瀏覽器:遞迴 reduce 方式
- 只有整數:
toString().split(',').map(Number)也行 - 樹形資料到平鋪列表:用遞迴,處理
children