前言
數(shù)組是 JS 中使用頻率僅次于對象的數(shù)據(jù)結(jié)構(gòu),官方提供了眾多的 API,今天我們來談?wù)勅绾伪馄交╢latten)數(shù)組。
顧名思義,扁平化就是將嵌套的數(shù)組變成一維數(shù)組的過程。
通常有幾種方法可以實現(xiàn)扁平化:
- 迭代遞歸法
- 曲線救國法
我們將以一個例子貫穿整篇文章:
var array = [[1,2,3],4,5,6,[[7]],[]]
var result = flatten(array)
console.log(result)
迭代遞歸
for...of 實現(xiàn)
function flatten(arr, result = []) {
for (let item of arr) {
if (Array.isArray(item))
flatten(item, result)
else
result.push(item)
}
return result
}
我們使用 result 變量存儲結(jié)果,然后迭代當(dāng)前數(shù)組,如果值也是數(shù)組則繼續(xù)扁平化,否則將值放入 result 里。
迭代器實現(xiàn)
眾所周知,數(shù)組在 JS 中是一種可迭代結(jié)構(gòu),所以我們可以利用這一點修改它的迭代器實現(xiàn)扁平化:
Array.prototype[Symbol.iterator] = function() {
let arr = [].concat(this)
const getFirst = function(array) {
let first = array[0]
// 去掉為 [] 的元素
while (Array.isArray(array[0]) && array.length === 0) {
array.shift()
}
if (Array.isArray(first)) {
// 即將是 []
if (first.length === 1) array.shift()
return getFirst(first)
} else {
array.shift()
return first
}
}
return {
next: function() {
let item = getFirst(arr)
if (item) {
return {
value: item,
done: false,
}
} else {
return {
done: true,
}
}
},
}
}
這里我們給數(shù)組的迭代器函數(shù)重新定義了 next 方法,實現(xiàn)了一個 getFirst 用來遞歸取真正的第一個數(shù)組元素(無論嵌套多少層),在對數(shù)組進行迭代操作的時候,會自動調(diào)用迭代器的 next 方法,獲得我們一個個基本元素。
不過這樣太麻煩了,還不如第一種方法方便呢!別急,下面那個實現(xiàn)才是我們想給大家看的~
生成器實現(xiàn)
迭代器的升級版就是生成器(Generator),其實這種扁平化最適合用生成器來做了,因為我們的目的就是生成一個個的值,然后把它們組織成一維數(shù)組:
function* flat(arr) {
for (let item of arr) {
if (Array.isArray(item))
yield* flat(item)
else
yield item
}
}
function flatten(arr) {
let result = []
for (let val of flat(arr)) {
result.push(val)
}
return result
}
是不是很簡潔明了?只需要定義一個生成器函數(shù):迭代當(dāng)前數(shù)組,如果值也是數(shù)組則生成扁平化的值,否則直接生成值。然后在我們的扁平化函數(shù)里調(diào)用這個生成器函數(shù)得到我們的一維數(shù)組。
這里有兩點需要注意:
- 嵌套 yield 需要再加一個星號,這被稱為生成器委托。
- 不能使用 forEach 代替 for...of 但可以用 for 循環(huán),因為 for 循環(huán)和for...of 可以中斷迭代去執(zhí)行 yield,forEach 不行,有興趣的讀者可以自己嘗試一下~
reduce 三句實現(xiàn)法
function flatten(arr) {
return arr.reduce((flat, toFlatten) => {
return flat.concat(Array.isArray(toFlatten) ? flatten(toFlatten) : toFlatten);
}, []);
}
reduce 是函數(shù)式編程兩大法寶之一,中文翻譯為化簡,用它來實現(xiàn),簡直是巧妙。
曲線救國法
這些方法大多是利用 JS 本身的一些特性和 API,算是奇技淫巧。
降維打擊法
function flatten(arr){
let str = arr.toString()
return str.split(',')
}
管你原來是幾維,先來個二向箔:轉(zhuǎn)成字符串,之后再復(fù)原成數(shù)組,不過這個方法有個缺點,就是原來的空數(shù)組轉(zhuǎn)的空字符串也會被放入新生成的數(shù)組里去。所以如果不需要空串元素的話還需要對結(jié)果進行過濾操作。
除了直接調(diào)用它的 toString 方法之外,還可以用隱式轉(zhuǎn)換間接調(diào)用:
function flatten(arr){
return (arr + '').split(',')
}
lodash 層次法
lodash 分為淺扁平化和深扁平化(deepFlatten)兩個方法。
- 淺扁平化就是只扁平化一層數(shù)組
- 深扁平化就是迭代調(diào)用淺扁平化函數(shù)
而淺扁平化有下列實現(xiàn)方法:
function shallowFlatten(arr){
return [].concat.apply([],arr)
}
或者
function shallowFlatten(arr) {
return arr.reduce((a, b) => a.concat(b), [])
}
所以我們最終實現(xiàn)的是:
function flatten(arr,n=1){
let result = arr
while(n--){
result = shallowFlatten(result)
}
return result
}
唯一變化的是調(diào)用方法從flatten(array)變成了flatten(array,2)
最后要說的
flatten 轉(zhuǎn)眼間就要成為 ES 的標(biāo)準(zhǔn)數(shù)組 API 了,但發(fā)生了很多有意思的事情