一文解析JavaScript中數(shù)組扁平化的各種實(shí)現(xiàn)方法
在現(xiàn)代前端開(kāi)發(fā)中,數(shù)組操作是日常編碼中最常見(jiàn)的任務(wù)之一。而在處理復(fù)雜數(shù)據(jù)結(jié)構(gòu)時(shí),我們經(jīng)常會(huì)遇到“嵌套數(shù)組”(即高維數(shù)組)的場(chǎng)景。例如,后端返回的數(shù)據(jù)結(jié)構(gòu)可能是多層嵌套的,我們需要將其“拍平”為一維數(shù)組以便于渲染或進(jìn)一步處理。這種將多層嵌套數(shù)組轉(zhuǎn)換為單層數(shù)組的過(guò)程,就被稱為 數(shù)組扁平化(Array Flattening)。
本文將帶你全面了解 JavaScript 中數(shù)組扁平化的各種方法,包括原生 API 的使用、遞歸實(shí)現(xiàn)、reduce 高階函數(shù)應(yīng)用、利用 toString 和 split 的巧妙技巧,以及基于展開(kāi)運(yùn)算符的循環(huán)優(yōu)化方案。我們將深入剖析每種方法的原理、優(yōu)缺點(diǎn)和適用場(chǎng)景,幫助你構(gòu)建完整的知識(shí)體系。
一、什么是數(shù)組扁平化
數(shù)組扁平化,顧名思義,就是把一個(gè)嵌套多層的數(shù)組“壓平”成一個(gè)只有一層的一維數(shù)組。例如:
const nestedArr = [1, [2, 3, [4, 5]], 6]; // 扁平化后應(yīng)得到: // [1, 2, 3, 4, 5, 6]
這個(gè)問(wèn)題看似簡(jiǎn)單,但在實(shí)際項(xiàng)目中非常常見(jiàn)。比如你在處理樹(shù)形菜單、評(píng)論回復(fù)結(jié)構(gòu)、文件目錄層級(jí)等數(shù)據(jù)時(shí),都可能需要對(duì)嵌套數(shù)組進(jìn)行扁平化處理。
二、使用原生flat()方法(推薦方式)
ES2019 引入了 Array.prototype.flat() 方法,使得數(shù)組扁平化變得極其簡(jiǎn)單和直觀。
基本語(yǔ)法
arr.flat([depth])
depth:指定要展開(kāi)的層數(shù),默認(rèn)為1。- 如果傳入
Infinity,則無(wú)論嵌套多少層,都會(huì)被完全展開(kāi)。
示例代碼
const arr = [1, [2, 3, [1]]]; console.log(arr.flat()); // [1, 2, 3, [1]] → 只展開(kāi)一層 console.log(arr.flat(2)); // [1, 2, 3, 1] → 展開(kāi)兩層 console.log(arr.flat(Infinity)); // [1, 2, 3, 1] → 完全展開(kāi)
特點(diǎn)總結(jié)
- 簡(jiǎn)潔高效:一行代碼解決問(wèn)題。
- 兼容性良好:現(xiàn)代瀏覽器基本都支持(IE 不支持)。
- 可控制深度:靈活控制展開(kāi)層級(jí)。
- 推薦用于生產(chǎn)環(huán)境:清晰、安全、性能好。
注意:flat() 不會(huì)改變?cè)瓟?shù)組,而是返回一個(gè)新的扁平化數(shù)組。
三、遞歸實(shí)現(xiàn):最經(jīng)典的思路
如果你不能使用 flat()(比如兼容老版本瀏覽器),或者想深入理解其內(nèi)部機(jī)制,那么遞歸是一個(gè)經(jīng)典且直觀的解決方案。
基礎(chǔ)遞歸版本
function flatten(arr) {
let res = [];
for (let i = 0; i < arr.length; i++) {
if (Array.isArray(arr[i])) {
res = res.concat(flatten(arr[i])); // 遞歸處理子數(shù)組
} else {
res.push(arr[i]); // 非數(shù)組元素直接加入結(jié)果
}
}
return res;
}
// 測(cè)試
const arr = [1, [2, 3, [1]]];
console.log(flatten(arr)); // [1, 2, 3, 1]
分析
使用 for 循環(huán)遍歷每個(gè)元素。
判斷是否為數(shù)組:是 → 遞歸調(diào)用;否 → 直接推入結(jié)果數(shù)組。
利用 concat 合并遞歸結(jié)果。
缺點(diǎn)
每次 concat 都會(huì)創(chuàng)建新數(shù)組,性能略低。
遞歸深度過(guò)大可能導(dǎo)致棧溢出(極端情況)。
四、使用reduce+ 遞歸:函數(shù)式編程風(fēng)格
利用 reduce 可以寫出更優(yōu)雅、更具函數(shù)式風(fēng)格的扁平化函數(shù)。
實(shí)現(xiàn)方式
function flatten(arr) {
return arr.reduce((pre, cur) => {
return pre.concat(Array.isArray(cur) ? flatten(cur) : cur);
}, []);
}
解析
reduce接收一個(gè)累加器pre和當(dāng)前元素cur。- 如果
cur是數(shù)組,則遞歸調(diào)用flatten(cur),否則直接使用cur。 - 使用
concat將結(jié)果合并到pre中。
優(yōu)點(diǎn)
- 代碼簡(jiǎn)潔,邏輯清晰。
- 更符合函數(shù)式編程思想。
- 易于組合其他操作(如 map、filter)。
五、利用toString()+split()的“黑科技”技巧
這是一個(gè)非常巧妙但需要謹(jǐn)慎使用的技巧,適用于數(shù)組中只包含數(shù)字或字符串基本類型的情況。
實(shí)現(xiàn)原理
JavaScript 中,數(shù)組的 toString() 方法會(huì)遞歸地將每個(gè)元素轉(zhuǎn)為字符串,并用逗號(hào)連接。
const arr = [1, [2, 3, [1]]]; console.log(arr.toString()); // "1,2,3,1"
我們可以利用這一點(diǎn),先轉(zhuǎn)成字符串,再用 split(',') 分割,最后通過(guò) +item 轉(zhuǎn)回?cái)?shù)字。
實(shí)現(xiàn)代碼
function flatten(arr) {
return arr.toString().split(',').map(item => +item);
}
// 測(cè)試
const arr = [1, [2, 3, [1]]];
console.log(flatten(arr)); // [1, 2, 3, 1]
優(yōu)點(diǎn)
- 代碼極短,實(shí)現(xiàn)“一行扁平化”。
- 性能較好(底層由引擎優(yōu)化)。
缺點(diǎn)
- 僅適用于純數(shù)字?jǐn)?shù)組:如果數(shù)組中有字符串
"hello",+"hello"會(huì)變成NaN。 - 無(wú)法保留原始類型:所有元素都會(huì)被轉(zhuǎn)為數(shù)字。
- 丟失
null、undefined、對(duì)象等復(fù)雜類型信息。
所以這個(gè)方法雖然巧妙,但不適合通用場(chǎng)景,僅作為面試中的“技巧”了解即可。
六、使用while循環(huán) +concat+ 展開(kāi)運(yùn)算符(性能優(yōu)化版)
這種方法避免了遞歸調(diào)用,采用循環(huán)逐步“拍平”數(shù)組,適合處理深層嵌套且希望避免棧溢出的場(chǎng)景。
實(shí)現(xiàn)方式
function flatten(arr) {
while (arr.some(item => Array.isArray(item))) {
arr = [].concat(...arr);
}
return arr;
}
原理解析
arr.some(item => Array.isArray(item)):檢查數(shù)組中是否還存在嵌套數(shù)組。...arr:展開(kāi)數(shù)組的所有元素。[].concat(...arr):concat會(huì)對(duì)展開(kāi)后的數(shù)組元素自動(dòng)“拍平一層”。
舉個(gè)例子:
[].concat(...[1, [2, 3, [1]]]) // 等價(jià)于 [].concat(1, [2, 3, [1]]) // → [1, 2, 3, [1]] → 拍平了一層
然后繼續(xù)循環(huán),直到?jīng)]有嵌套為止。
優(yōu)點(diǎn)
- 非遞歸,避免棧溢出。
- 邏輯清晰,易于理解。
- 性能較好,尤其適合中等深度嵌套。
缺點(diǎn)
- 每次
concat(...arr)都會(huì)創(chuàng)建新數(shù)組,內(nèi)存開(kāi)銷較大。 - 對(duì)于極深嵌套,仍可能影響性能。
七、對(duì)比總結(jié):各種方法的適用場(chǎng)景
| 方法 | 優(yōu)點(diǎn) | 缺點(diǎn) | 推薦場(chǎng)景 |
|---|---|---|---|
| arr.flat(Infinity) | 簡(jiǎn)潔、標(biāo)準(zhǔn)、安全 | IE 不支持 | 生產(chǎn)環(huán)境首選 |
| 遞歸 + for | 邏輯清晰,易理解 | 性能一般,可能棧溢出 | 學(xué)習(xí)理解原理 |
| reduce + 遞歸 | 函數(shù)式風(fēng)格,優(yōu)雅 | 同上 | 偏好函數(shù)式編程 |
| toString + split | 代碼短,性能好 | 類型受限,不通用 | 面試技巧 |
| while + concat + ... | 非遞歸,避免棧溢出 | 內(nèi)存占用高 | 深層嵌套處理 |
八、擴(kuò)展思考:如何實(shí)現(xiàn)深度可控的扁平化
有時(shí)候我們并不想完全拍平,而是只想展開(kāi)指定層數(shù)??梢苑抡?flat(depth) 實(shí)現(xiàn)一個(gè)通用函數(shù):
function flattenDepth(arr, depth = 1) {
if (depth === 0) return arr.slice(); // 深度為0,直接返回副本
let result = [];
for (let item of arr) {
if (Array.isArray(item) && depth > 0) {
result.push(...flattenDepth(item, depth - 1));
} else {
result.push(item);
}
}
return result;
}
// 測(cè)試
const arr = [1, [2, 3, [4, 5, [6]]]];
console.log(flattenDepth(arr, 1)); // [1, 2, 3, [4, 5, [6]]]
console.log(flattenDepth(arr, 2)); // [1, 2, 3, 4, 5, [6]]
console.log(flattenDepth(arr, Infinity)); // [1, 2, 3, 4, 5, 6]
九、結(jié)語(yǔ)
小貼士:如果你的項(xiàng)目需要兼容老舊瀏覽器,可以使用 Babel 轉(zhuǎn)譯 flat(),或手動(dòng)引入 polyfill:
// Polyfill for Array.prototype.flat
if (!Array.prototype.flat) {
Array.prototype.flat = function(depth = 1) {
return this.reduce((acc, val) =>
Array.isArray(val) && depth > 0
? acc.concat(val.flat(depth - 1))
: acc.concat(val)
, []);
};
}
這樣就能在任何環(huán)境中愉快地使用 flat() 了!
到此這篇關(guān)于一文解析JavaScript中數(shù)組扁平化的各種實(shí)現(xiàn)方法的文章就介紹到這了,更多相關(guān)JavaScript數(shù)組扁平化內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
一個(gè)對(duì)于js this關(guān)鍵字的問(wèn)題
一個(gè)對(duì)于js this關(guān)鍵字的問(wèn)題...2007-01-01
js的.innerHTML = ""IE9下顯示有錯(cuò)誤的解決方法
js的.innerHTML= "……"在ie9- 的版本顯示不正常,使用jquery可以解決,有類似問(wèn)題的朋友可以參考下2013-09-09
JavaScript實(shí)現(xiàn)動(dòng)態(tài)表單生成
這篇文章主要來(lái)和大家一起深入探討如何使用JavaScript實(shí)現(xiàn)一個(gè)動(dòng)態(tài)表單生成器,文中的示例代碼講解詳細(xì),有需要的小伙伴可以參考一下2024-01-01
js將滾動(dòng)條滾動(dòng)到指定位置的簡(jiǎn)單實(shí)現(xiàn)方法
下面小編就為大家?guī)?lái)一篇js將滾動(dòng)條滾動(dòng)到指定位置的簡(jiǎn)單實(shí)現(xiàn)方法。小編覺(jué)得挺不錯(cuò)的, 現(xiàn)在就分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧2016-06-06
js前端傳json后臺(tái)接收‘‘被轉(zhuǎn)為quot的問(wèn)題解決
這篇文章主要介紹了js前端傳json后臺(tái)接收‘‘被轉(zhuǎn)為"的問(wèn)題解決,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2020-11-11
js實(shí)現(xiàn)點(diǎn)擊展開(kāi)隱藏效果(實(shí)例代碼)
本篇文章小編給大家?guī)?lái)一篇關(guān)于JS實(shí)現(xiàn)點(diǎn)擊展開(kāi)點(diǎn)擊隱藏的效果代碼內(nèi)容,有興趣的朋友參考下。2018-09-09
JavaScript調(diào)試之console.log調(diào)試的一個(gè)小技巧分享
日常開(kāi)發(fā)中經(jīng)常會(huì)需要console來(lái)查看當(dāng)前對(duì)象的值。當(dāng)然用debugger會(huì)更全面的查看,但是總有只喜歡用console的,比如我。下面這篇文章主要給大家分享了關(guān)于JavaScript調(diào)試之console.log調(diào)試的一個(gè)小技巧,需要的朋友可以參考借鑒,下面來(lái)一起看看吧。2017-08-08
VSCode中 Eslint 和 Prettier 沖突問(wèn)題最新解決方法
這篇文章主要介紹了VSCode中 Eslint和Prettier沖突問(wèn)題,既然使用 eslint規(guī)范,我的理解上當(dāng)然是不能去修改eslint,因?yàn)檫@是共用規(guī)范,修改了就沒(méi)有再使用的必要了,所以我是不推薦修改eslint的方式,本文給結(jié)合實(shí)例代碼給大家詳細(xì)講解,需要的朋友可以參考下2023-02-02

