如何使用 JavaScript 扁平化数组?
「如何」系列第三篇,分享了6个JS扁平化数组的方法,总结了3个核心思想。
一、数组扁平化
数组扁平化就是将多维数组转化为一维数组:
let arr = [1, [2, 3, [4, 5]]] ——> [1, 2, 3, 4, 5]
1.1 toString + split
先将数组转化为字符串,再使用split将字符串转化为数组:
let arr = [1, [2, 3, [4, 5]]]
function flatten(arr) {
return arr.toString().split(',')
}
console.log(flatten(arr))
用split形成的数组的每个元素仍然是字符串,需要将其转化为数字
缺陷 :若元素为字符串且包含「 , 」,则 split 方法不能正确分割。
1.2 reduce
reduce方法会对根据回调函数对数组的每个元素进行操作:
let arr = [1, [2, 3, [4, 5]]]
const newArr = function(arr){
return arr.reduce((pre,cur)=>pre.concat(Array.isArray(cur)?newArr(cur):cur),[])
}
console.log(newArr(arr));
更多reduce()方法的使用:《数组reduce()方法的妙用》
1.3 join+split
join()方法和上面的toString方法类似,都能将数组转化为字符串
let arr = [1, [2, 3, [4, 5]]]
function flatten(arr) {
return arr.join(',').split(',')
}
注意 :若被扁平化的数组由数字 构成,join在扁平化数组的同时,使用传入的第一个参数作为分隔符,输出的是扁平化后的数组字符串,所以需要先用split切分转为字符串数组,再对每个元素进行转数字操作。
1.4 递归 + 循环
function myFlat(arr){
let res = []
arr.map(item=>{
if(Array.isArray(item)){
res = res.concat(myFlat(item))
} else {
res.push(item)
}
})
return res
}
1.5 拓展运算符
对数组进行多次判断,若有任意元素是数组,则使用...将其解构一次,反复执行以上过程,直至数组扁平化完成。
Array.some返回一个Boolean值,表示数组中是否有任一元素通过测试函数
...扩展运算符可以取出参数的所有的可以遍历的对象,并拷贝到当前的对象中
let arr = [1, [2, 3, [4, 5]]]
function myFlat(arr) {
while (arr.some(item => Array.isArray(item))) {
arr = [].concat(...arr);
}
return arr;
}
console.log(myFlat(arr));
1.6 flat
flat()是 ES6 中的一个数组方法,该方法会按照一个可指定的深度depth递归遍历数组,并将所有元素与遍历到的子数组中的元素合并为一个新数组返回。
语法: let newArray = arr.flat(depth)
注意:
-
该方法不会改变原数组
-
depth为指定要提取嵌套数组的结构深度,默认值为 1 -
flat()方法会移除数组中的空项let arr1 = [1, 2, [3, 4]]; arr1.flat(); //[1, 2, 3, 4]
let arr2 = [1, 2, [3, 4, [5, 6]]]; arr2.flat(1); // [1, 2, 3, 4, [5,6]]
let arr3 = [1, 2, [3, 4, [5, 6]]]; arr3.flat(2); // [1, 2, 3, 4, 5, 6]
let arr4 = [1, 2, [3, 4, [5, 6]]] arr4.flat(Infinity); //[1,2,3,4,5,6]
let arr5 = [1, 2, , 4, 5]; arr5.flat(); //[1,2,4,5]
二、归纳
数组扁平化方法的核心思想,我总结了以下三种
2.1 层次思想
分别实现两个方法
- 浅扁平化方法:只扁平化一层数组
例如:
function shallowFlatten(arr) {
return [].concat(...arr)
}
-
深扁平化方法:迭代 执行浅扁平化方法
function deepFlatten(arr,deepth){ let result = arr while(n--){ result = shallowFlatten(result) } return result }
使用方法和flat类似
let arr = [1, [2, 3, [4, 5]]]
deepFlatten(arr,1)
//[1,,2,3,[4,5]]
deepFlatten(arr,2)
//[1,2,3,4,5]
上文提到的 1.5 拓展运算符 使用了这种思想。
2.2 递归思想
1.2 reduce、1.4 递归 + 循环都是递归思想的不同实现,根据数组方法的使用差异,还可以有更多种实现,篇幅有限,不一一列举。
这里用1.2 reduce作为例子描述一下递归的核心思想:
function myFlat(arr){
//定义一个临时数组 res
let res = []
//循环遍历原数组
arr.map(item =>{
if(Array.isArray(item)){//若当前元素是数组
//递归使用myFlat方法展开此数组,将返回值和此数组拼接
res = res.concat(myFlat(item))
} else {//若当前元素不是数组,则已经访问到最底层
//将此非数组元素塞入 res 尾部
res.push(item)
}
})
//返回临时数组
return res
}
2.3 降维打击
1.1 toString + spli 、1.3 join+split属于奇淫巧计,管你原来在第几层,我直接把你拉到第一层:转成字符串,之后再复原成数组。不过这个方法有个缺点,就是原来的空数组转的空字符串 也会被放入新生成的数组里去。如果不需要空字符串元素,就需要对结果进行过滤。
除了直接调用它的 toString 方法之外,还可以用隐式转换间接调用:
function flatten(arr){
return (arr + '').split(',')
}
三、最后
实际项目中需要使用的话,flat是代码量最小最直接的一种方法;遇上面试考察当然是多多益善。如果考虑到性能,我个人理解降维打击思想 在理论上性能最好。下一篇文章将和各位一同学习对象扁平化 。
参考: