JS 扁平化(flatten) 数组
前言
数组是 JS 中使用频率仅次于对象的数据结构,官方提供了众多的 API,今天我们来谈谈如何扁平化(flatten)数组。
顾名思义,扁平化就是将嵌套的数组变成一维数组的过程。
通常有几种方法可以实现扁平化:
- 迭代递归法
- 曲线救国法
我们将以一个例子贯穿整篇文章:
var array = [[1,2,3],4,5,6,[[7]],[]]
var result = flatten(array)
console.log(result)
迭代递归
for...of 实现
function flatten(arr, result = []) {
for (let item of arr) {
if (Array.isArray(item))
flatten(item, result)
else
result.push(item)
}
return result
}
我们使用 result 变量存储结果,然后迭代当前数组,如果值也是数组则继续扁平化,否则将值放入 result 里。
迭代器实现
众所周知,数组在 JS 中是一种可迭代结构,所以我们可以利用这一点修改它的迭代器实现扁平化:
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,
}
}
},
}
}
这里我们给数组的迭代器函数重新定义了 next 方法,实现了一个 getFirst 用来递归取真正的第一个数组元素(无论嵌套多少层),在对数组进行迭代操作的时候,会自动调用迭代器的 next 方法,获得我们一个个基本元素。
不过这样太麻烦了,还不如第一种方法方便呢!别急,下面那个实现才是我们想给大家看的~
生成器实现
迭代器的升级版就是生成器(Generator),其实这种扁平化最适合用生成器来做了,因为我们的目的就是生成一个个的值,然后把它们组织成一维数组:
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
}
是不是很简洁明了?只需要定义一个生成器函数:迭代当前数组,如果值也是数组则生成扁平化的值,否则直接生成值。然后在我们的扁平化函数里调用这个生成器函数得到我们的一维数组。
这里有两点需要注意:
- 嵌套 yield 需要再加一个星号,这被称为生成器委托。
- 不能使用 forEach 代替 for...of 但可以用 for 循环,因为 for 循环和for...of 可以中断迭代去执行 yield,forEach 不行,有兴趣的读者可以自己尝试一下~
reduce 三句实现法
function flatten(arr) {
return arr.reduce((flat, toFlatten) => {
return flat.concat(Array.isArray(toFlatten) ? flatten(toFlatten) : toFlatten);
}, []);
}
reduce 是函数式编程两大法宝之一,中文翻译为化简,用它来实现,简直是巧妙。
曲线救国法
这些方法大多是利用 JS 本身的一些特性和 API,算是奇技淫巧。
降维打击法
function flatten(arr){
let str = arr.toString()
return str.split(',')
}
管你原来是几维,先来个二向箔:转成字符串,之后再复原成数组,不过这个方法有个缺点,就是原来的空数组转的空字符串也会被放入新生成的数组里去。所以如果不需要空串元素的话还需要对结果进行过滤操作。
除了直接调用它的 toString 方法之外,还可以用隐式转换间接调用:
function flatten(arr){
return (arr + '').split(',')
}
lodash 层次法
lodash 分为浅扁平化和深扁平化(deepFlatten)两个方法。
- 浅扁平化就是只扁平化一层数组
- 深扁平化就是迭代调用浅扁平化函数
而浅扁平化有下列实现方法:
function shallowFlatten(arr){
return [].concat.apply([],arr)
}
或者
function shallowFlatten(arr) {
return arr.reduce((a, b) => a.concat(b), [])
}
所以我们最终实现的是:
function flatten(arr,n=1){
let result = arr
while(n--){
result = shallowFlatten(result)
}
return result
}
唯一变化的是调用方法从flatten(array)
变成了flatten(array,2)
最后要说的
flatten 转眼间就要成为 ES 的标准数组 API 了,但发生了很多有意思的事情