开篇思考
- 怎样用最简单/普通的方式解决数组扁平化问题?
- ES6 中是否有一些高级的方法能直接实现?
扁平化实现思路
本质上将一个嵌套多层(任何层数)的数组转换为只有一层的数组。如下所示:
let arr = [12, 5, [8, 99, 200]];
console.log(flatten(arr)); // [12, 5, 8, 99, 200]
简单来说就是用一个 flatten 函数把多维数组“拍平”,然后输出一维数组。接下来就尝试着实现一个 flatten 函数吧。
方法一:普通递归
普通递归思路比较容易理解,就是通过循环递归方法,一项一项遍历,如果遍历的当前项还是一个数组,那就继续往下遍历,利用递归方法,来实现数组每一项的连接。如下所示。
let arr = [12, 5, [8, 99, 200]];
function flatten(arr) {
let result = [];
for(let i = 0; i < arr.length; i++) {
if(Array.isArray(arr[i])) {
result = result.concat(flatten(arr[i]));
} else {
result.push(arr[i]);
}
}
return result;
}
console(flatten(arr)); // [12, 5, 8, 99, 200]
以上代码核心就是循环遍历过程中的递归操作,在遍历的过程中发现数组元素还是数组的时候进行递归操作,把数组的结果通过 concat 方法拼接到最后要返回的 result 数组上,最后输出得到扁平化后数组。接下来看看下一种实现方式。
方法二:reduce 函数迭代
从以上递归函数中可以看出,无非就是对数组每一项进行处理,那么也可以用 reduce 来实现数组的拼接,还能简化第一种方法的代码,改造后如下所示。
let arr = [12, 5, [8, 99, 200]];
function flatten(arr) {
return arr.reduce(function(prev, next) {
return prev.concat(Array.isArray(next) ? flatten(next) : next);
}, []);
}
console(flatten(arr)); // [12, 5, 8, 99, 200]
用 reduce 的第一个参数用来返回最后的累加结果,思路和第一种递归方法思路是一样的,但使用 reduce 后代码更简洁了,也解决了数组扁平化的问题。接下来看看下一种实现方式。
方法三:扩展运算符
使用扩展运算符和 some 的方法,二者一起使用,达到扁平化目的。
let arr = [12, 5, [8, 99, 200]];
function flatten(arr) {
while(arr.some(item => Array.isArray(item))) {
arr = [].concat(...arr);
}
return arr;
}
console(flatten(arr)); // [12, 5, 8, 99, 200]
从代码执行过程可以看出,先用数组的 some 方法把数组中依然是数组项过滤,然后执行 concat 操作,利用 ES6 的展开运算符,将其拼接到原数组中,最后返回原数组,从而达到预期效果。
前三种方式本质上还是通过普通递归思路衍生的方法,前两种比较类似,reduce 方法提供的几个参数比较灵活,可以解决的问题很多,值得熟练使用并精通。
除此之外,再试试其他实现方式。
方法四:split 和 toString 一起处理
通过 split 和 toString 两个方法组合使用实现数组扁平化,由于数组默认带一个 toString 方法,因此可以把数组直接转换成逗号分隔符的字符串,然后再使用 split 方法把字符串重新转换成数组。
let arr = [12, 5, [8, 99, 200]];
function flatten(arr) {
return arr.toString().split(',');
}
console(flatten(arr)); // [12, 5, 8, 99, 200]
通过种两种方法可以直接将多维数组直接转换成逗号连接的字符串,然后再重新分隔成数组。
接下来再试试使用 ES6 中的方法来实现。
方法五:ES6 中的 flat
先看看 flat 语法:
arr.flat([depth])
depth 是 flat 参数,参数含义是数组的展开深度(默认不填数值是 1),那无论多少数组多少层都要展开怎么处理呢?这时候传 Infinity。
let arr = [12, [5, [8, 99, 200]]];
function flatten(arr) {
return arr.flat(Infinity);
}
console(flatten(arr)); // [12, 5, 8, 99, 200]
这里因为数组的层级是确定的,因此 flat 参数传 2,也能达到想要的效果,但如果日常开发中对数组嵌套层级不确定的情况下,最好使用 Infinity 比较稳妥。
方法六:正则和 JSON 方法一起处理
在第四种方法中,使用过 toString 方法,这里仍然使用 JSON.stringify 的方法先转换为字符串,然后通过正则表达式过滤字符串中的方括号,最后再利用 JSON.parse 把它转换成数组。
let arr = [12, [5, [8, [99, 200]]], 999];
function flatten(arr) {
let str = JSON.stringify(arr);
str = str.replace(/(\[|\]))/g, '');
str = '[' + str + ']';
return JSON.parse(str);
}
console(flatten(arr)); // [12, 5, 8, 99, 200, 999]
这里利用正则表达式匹配规则,全局匹配(g)左括号或右括号,将其替换成空格,然后拿着正则处理好的结果重新在外层包裹括号,最后通过 JSON.parse 转换成数组返回。
总结
以上就是日常开发中有可能遇到数组扁平化的几种方法,涉及到数组 API、ES6,以及 JSON 方法的相关知识。通过下面表格再看看这六种方式的实现思路。
| 方法 | 实现难度 | 编码思路 |
|---|---|---|
| 递归实现 | 易 | 递归实现,返回新数组 |
| reduce 实现 | 中 | reduce 进行累加操作 |
| 扩展运算符实现 | 中 | 筛选数组项进行连接 |
| split 和 toString | 易 | 转成字符串再转数组 |
| flat 方法 | 易 | 特定功能方法直接操作 |
| 正则和 JSON 方法 | 中 | JSON 方法转成字符串转回过程中正则处理 |
在日常业务开发中往往会遇到各种数组问题,所有要思考最合适的解决方法。