一文解析JavaScript中數(shù)組扁平化的各種實(shí)現(xiàn)方法
在現(xiàn)代前端開發(fā)中,數(shù)組操作是日常編碼中最常見的任務(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ù)組的過程,就被稱為 數(shù)組扁平化(Array Flattening)。
本文將帶你全面了解 JavaScript 中數(shù)組扁平化的各種方法,包括原生 API 的使用、遞歸實(shí)現(xiàn)、reduce
高階函數(shù)應(yīng)用、利用 toString
和 split
的巧妙技巧,以及基于展開運(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è)問題看似簡(jiǎn)單,但在實(shí)際項(xiàng)目中非常常見。比如你在處理樹形菜單、評(píng)論回復(fù)結(jié)構(gòu)、文件目錄層級(jí)等數(shù)據(jù)時(shí),都可能需要對(duì)嵌套數(shù)組進(jìn)行扁平化處理。
二、使用原生flat()方法(推薦方式)
ES2019 引入了 Array.prototype.flat()
方法,使得數(shù)組扁平化變得極其簡(jiǎn)單和直觀。
基本語法
arr.flat([depth])
depth
:指定要展開的層數(shù),默認(rèn)為1
。- 如果傳入
Infinity
,則無論嵌套多少層,都會(huì)被完全展開。
示例代碼
const arr = [1, [2, 3, [1]]]; console.log(arr.flat()); // [1, 2, 3, [1]] → 只展開一層 console.log(arr.flat(2)); // [1, 2, 3, 1] → 展開兩層 console.log(arr.flat(Infinity)); // [1, 2, 3, 1] → 完全展開
特點(diǎn)總結(jié)
- 簡(jiǎn)潔高效:一行代碼解決問題。
- 兼容性良好:現(xiàn)代瀏覽器基本都支持(IE 不支持)。
- 可控制深度:靈活控制展開層級(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ù)組,性能略低。
遞歸深度過大可能導(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(',')
分割,最后通過 +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
。 - 無法保留原始類型:所有元素都會(huì)被轉(zhuǎn)為數(shù)字。
- 丟失
null
、undefined
、對(duì)象等復(fù)雜類型信息。
所以這個(gè)方法雖然巧妙,但不適合通用場(chǎng)景,僅作為面試中的“技巧”了解即可。
六、使用while循環(huán) +concat+ 展開運(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
:展開數(shù)組的所有元素。[].concat(...arr)
:concat
會(huì)對(duì)展開后的數(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)存開銷較大。 - 對(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í)候我們并不想完全拍平,而是只想展開指定層數(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é)語
小貼士:如果你的項(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)文章
JavaScript隊(duì)列結(jié)構(gòu)Queue實(shí)現(xiàn)過程解析
這篇文章主要介紹了JavaScript隊(duì)列結(jié)構(gòu)Queue實(shí)現(xiàn)過程解析,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下2020-03-03如何在JavaScript中等分?jǐn)?shù)組的實(shí)現(xiàn)
這篇文章主要介紹了如何在JavaScript中等分?jǐn)?shù)組的實(shí)現(xiàn),文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2020-12-12JavaScript實(shí)現(xiàn)頁面跳轉(zhuǎn)的八種方式
這篇文章介紹了JavaScript實(shí)現(xiàn)頁面跳轉(zhuǎn)的八種方式,文中通過示例代碼介紹的非常詳細(xì)。對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2022-06-06chrome瀏覽器如何斷點(diǎn)調(diào)試異步加載的JS
chrome瀏覽器不僅僅可以用來上網(wǎng),對(duì)于開發(fā)人員來說,它更像是一款強(qiáng)大的開發(fā)輔助工具。今天這篇文章給大家分享在chrome瀏覽器如何斷點(diǎn)調(diào)試異步加載的JS,有需要的可以參考借鑒。2016-09-09JS自定義對(duì)象實(shí)現(xiàn)Java中Map對(duì)象功能的方法
這篇文章主要介紹了JS自定義對(duì)象實(shí)現(xiàn)Java中Map對(duì)象功能的方法,可實(shí)現(xiàn)類似Java中Map對(duì)象增刪改查等功能,具有一定參考借鑒價(jià)值,需要的朋友可以參考下2015-01-01javascript 最常用的10個(gè)自定義函數(shù)[推薦]
如果不使用類庫或者沒有自己的類庫,儲(chǔ)備一些常用函數(shù)總是有好處的。2009-12-12JavaScript實(shí)現(xiàn)的級(jí)聯(lián)算法示例【省市二級(jí)聯(lián)動(dòng)功能】
這篇文章主要介紹了JavaScript實(shí)現(xiàn)的級(jí)聯(lián)算法,結(jié)合省市二級(jí)聯(lián)動(dòng)下拉菜單功能實(shí)例分析了javascript事件響應(yīng)與元素動(dòng)態(tài)操作實(shí)現(xiàn)級(jí)聯(lián)算法的相關(guān)技巧,需要的朋友可以參考下2018-12-12