引子:这些日子在看es相关的新属性是有一个数组的方法 flat() 引起的我的注意。
1 什么是扁平化?
我去找了一下维基百科,上面并没有关于扁平化的直接解释,只有一个扁平化组织释义。
先来说一下,与扁平化对立的组织:金字塔组织,这个众所周知,它表现的层级结构就是一个金字塔式的形状。
扁平化组织(Flat organization)也被称为横向组织(horizontal organization),是一种在员工和Boss之间很少存在或不存在中间管理层的组织。
左侧就是金字塔组织,右侧就是扁平化组织。可见扁平化组织的层级是很少的,基层里组成单位(人)是最多的。
2 什么是数组扁平化
[‘a’,‘b’,‘c’] //这是一个拥有3个元素的数组,是一个一维数组(不存在数组嵌套)。[[‘a’,‘b’],[‘c’,‘d’],[‘e’,‘f’]] 从整体上看是一个数组,但是其中的元素又是数组,即数组中嵌套数组,这就是二维数组
以此类推·····
[‘a’,[‘b’,[‘c’]]]//3维数组 [‘a’,[‘b’,[‘c’,[…]]]]//n维数组 数组扁平化就是把多维数组转化成一维数组。(可以联想上图。。。类比于数组的展开)
3 数组扁平化的方法
3.1 es6提供的新方法flat(depth)
let a = [1,[2,3]];
a.flat(); // [1,2,3]
a.flat(1); //[1,2,3]
flat(depth) 方法中的参数depth,代表展开嵌套数组的深度,默认是1
所以我们可以添加参数1,或者直接调用flat()来对2维数组进行扁平化,如果我们可以提前知道数组的维度,对这个数组进行扁平化处理,参数depth的值就是数组的维度减一。
let a = [1,[2,3,[4,[5]]]];
a.flat(4-1); // [1,2,3,4,5] a是4维数组
其实还有一种更简单的办法,无需知道数组的维度,直接将目标数组变成1维数组。depth的值设置为Infinity。
let a = [1,[2,3,[4,[5]]]];
a.flat(Infinity); // [1,2,3,4,5] a是4维数组
3.2 for循环
var arr1 = [1, 2, 3, [1, 2, 3, 4, [2, 3, 4]]];
function flatten(arr) {
var res = [];
for(let i = 0, length = arr.length; i < length; i++) {
if(Array.isArray(arr[i])) {
res = res.concat(flatten(arr[i])); //concat 并不会改变原数组
//res.push(...flatten(arr[i])); //扩展运算符
} else{
res.push(arr[i]);
}
}
return res;
}
flatten(arr1); //[1, 2, 3, 1, 2, 3, 4, 2, 3, 4]
利用for循环遍历数组的每一项并加以判断,如果不是数组,就执行push操作, 是数组的化,就再次执行该函数(递归),直至遍历完整个数组。
ps: ...和concat()可以进行替换,所以完全可以算是2种方法。
3.3 while循环
var arr1 = [1, 2, [3], [1, 2, 3, [4, [2, 3, 4]]]];
function flatten(arr) {
while(arr.some(item => Array.isArray(item))) {
arr = [].concat(...arr);
//arr = Array.prototype.concat.apply([],arr);
}
return arr;
}
flatten(arr1); //[1, 2, 3, 1, 2, 3, 4, 2, 3, 4]
同理,利用while判断加上some的遍历来实现扁平化。
3.4 reduce方法
var arr1 = [1, 2, [3], [1, 2, 3, [4, [2, 3, 4]]]];
function flatten(arr) {
return arr.reduce((res,next) =>{
return res.concat(Array.isArray(next)? flatten(next) : next);
},[]);
}
这里使用的是数组的reduce方法,需要注意的是reduce方法,我们传递了两个参数, 第一个参数就是就是处理扁平化的箭头函数 第二个参数是一个空数组,也是作为遍历的开始。(res)
3.5 使用 stack 无限反嵌套多层嵌套数组
var arr1 = [1, 2, [3], [1, 2, 3, [4, [2, 3, 4]]]];
function flatten(input) {
const stack = [...input]; //保证不会破坏原数组
const result = [];
while(stack.length) {
const first = stack.shift();
if(Array.isArray(first)) {
stack.unshift(...first);
} else{
result.push(first);
}
}
return result;
}
flatten(arr1); //[1, 2, 3, 1, 2, 3, 4, 2, 3, 4]
这种方法其实并没有多复杂,这里没有使用递归, 原理判断stack数组长度,从数组从前往后拆出每一项(此时stack数组长度减一), 并进行判断,如果不是数组,执行push操作,该项被移除, 是数组的化,数组展开后,执行unshift操作,添加到stack数组的前面,此时stack的数组的长度会增加,这样不断的进行判断和扩展,直至每一项被拆出。
ps: 1 开始的[…input]是为了保证传进来的数组不会被破坏。
2 上面的shift(),unshift()可以换成pop()和push(),只不过return前要执行reverse()操作
3.6 如果数组的项全为数字,可以使用join(),toString()
如果数组的项全为数字,可以使用join(),toString()进行扁平化操作。
function flatten(input) {
return input.toString().split(',').map(item => +item);
// return input.join().split(',').map(item => +item);
// return input.join(',').split(',').map(item => +item);
}
flatten(arr1); //[1, 2, 3, 1, 2, 3, 4, 2, 3, 4]
原理很简单,先把数组转换成字符串,这个过程会把[]去掉,然后再调用split()方法转换成数组,最后不能忘了,把每一项转换为数组,即调用map()方法。
ps:数组的toString()方法是定义在Array.prototype(原型)上的,会对数组每一项执行toString()
然后在拼接成一个字符串,所以即使数组是嵌套的,里面嵌套的数组也会执行toString方法。
----这里是我个人的理解。
上面的解释也是不严谨的,肯定会有大佬说,undefined和null上并没有toString()方法,
[null,undefined].toString();//"null,undefined"
这里可以理解js引擎做了判断,执行了String()方法。保证数组的toString()方法不会报错。
4 总结
数组扁平化的方法是很多种的,如果要强行使用call,apply等,可能还要在加上几种,除了flat()以外,其余的方法,都要遍历数组,进行判断,除了3.5这种方法外,基本上都要递归。等有时间,在补充下,各个方法的性能区别,如果只是数字,建议用最后的方法。
文中若有不对的地方,请各位大佬积极指正,及时改进,共同进步。
有缘再会。。。。。。。
更新
数组的es6的新方法flatMap();可以用来展开2维数组。flatMap()方法类似先执行数组的map()(相当于执行Array.prototype.map()),然后对返回值组成的数组执行flat()方法(展开一层)。
var arr = [["今", "天", "天", "气", "不", "错"],[""],["早", "上", "好"]];
arr.flatMap(item => item);
//["今", "天", "天", "气", "不", "错", "", "早", "上", "好"]
flatMap()与map()不同,并不会改变原数组
5 后续性能问题
5.1 生成n维数组
let arr =[];
for(let i = 199; i> 0;i--){
arr = [i].concat([arr]); //生成一个199维的数组
}
这里生成199维的数组,而不是更大的数组,主要有两个原因:1、 实际项目中不会出现很深维度的数组,即使是JSON数据,也不太可能超过10维,毕竟解析起来,很繁琐。2、 这里是为了方便测试,如果设置的维度太深,例如9999,那么有些扁平化数组的方法就会报错。
这个错误是说栈溢出。一般出现在递归调用或者死循环。所以上面采用了199维。
5.2 性能测试
不多说废话,我采用的方法是console.time()/timeEnd()来生成执行函数扁平化的时间。结果如下:
代码不贴了,留一个 github地址(https://github.com/q547114121/juejin/blob/master/one.html) ,有兴趣的复制自测下。结论是:
1、性能最好的是es6的falt()
2、最差的就是while() reduce()
3、剩下的方法差距都不是很大。
如果有可能支持新属性,最好使用新属性,或者stack()方法。
最后贴一下支持flat()的浏览器:
IE浏览器是不支持这个方法的。Chrome也要升级到69以上。所以使用该方法时,一定要注意这个问题。
作者:六翅兽~
原文链接:http://suo.im/6vYjwj
来源:掘金