如何使用 JavaScript 扁平化数组?

2021/06/08

「如何」系列第三篇,分享了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 reduce1.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 + spli1.3 join+split属于奇淫巧计,管你原来在第几层,我直接把你拉到第一层:转成字符串,之后再复原成数组。不过这个方法有个缺点,就是原来的空数组转的空字符串 也会被放入新生成的数组里去。如果不需要空字符串元素,就需要对结果进行过滤。

除了直接调用它的 toString 方法之外,还可以用隐式转换间接调用:

function flatten(arr){
    return (arr + '').split(',')
}

三、最后

实际项目中需要使用的话,flat是代码量最小最直接的一种方法;遇上面试考察当然是多多益善。如果考虑到性能,我个人理解降维打击思想 在理论上性能最好。下一篇文章将和各位一同学习对象扁平化

参考:

js 数组扁平化

Array.prototype.reduce()

Array.prototype.some()

JS 扁平化(flatten) 数组