JavaScript数组扁平化实现方案与Underscore源码解析

作者:木棉

时间:2026-10-25

浏览:300

本文详细解析JavaScript中数组扁平化的多种实现方式,包括循环递归、toString转换、reduce累加器及ES6扩展运算符。深入剖析Underscore库中flatten函数的源码逻辑,重点讲解shallow与strict参数的配置对扁平化行为的影响,并探讨其在_.union和_.difference等API中的应用场景。

文章目录

什么是扁平化

数组扁平化的核心概念,是将一个可能包含任意层级嵌套结构的数组,转换为一个仅包含单层元素的数组。

假设存在一个名为 flatten 的函数用于执行此操作,其处理效果示例如下:


var arr = [1, [2, [3, 4]]];
console.log(flatten(arr)) // [1, 2, 3, 4]
循环数组+递归

循环数组+递归

实现逻辑:遍历数组元素,若检测到子元素仍为数组,则递归调用扁平化函数(基于for循环逻辑),并通过concat方法合并结果,最终返回处理后的数组。


function flatten(arr){
     var result = [];
     for(var i = 0, len = arr.length; i < len; i++){
         if(Array.isArray(arr[i])){
             result = result.concat(flatten(arr[i]));
         }else{
             result.push(arr[i]);
         }
     }
     return result;
 }
 
flatten(arr)   // [1,2,3,4]

递归

最直观的思路是遍历数组元素,若发现元素类型为数组,则对该元素递归调用扁平化方法:


   var arr = [1, [2, [3, 4]]];
     
    function flatten(arr) {
        var result = [];
        for (var i = 0, len = arr.length; i < len; i++) {
            if (Array.isArray(arr[i])) {
                result = result.concat(flatten(arr[i]))
            }
            else {
                result.push(arr[i])
            }
        }
        return result;
    }
 
 
console.log(flatten(arr))

tostring

若数组内仅包含数字元素,可尝试利用 toString 方法,因为:

[1, [2, [3, 4]]].toString() // “1,2,3,4”

调用 toString 后会得到一个由逗号分隔的扁平字符串,此时再使用 split 分割并转换为数字类型,即可实现扁平化。


// 方法2
var arr = [1, [2, [3, 4]]];
 
function flatten(arr) {
    return arr.toString().split(',').map(function(item){
        return +item
    })
}
 
console.log(flatten(arr))

然而该方法的适用场景极为有限,例如当数组包含字符串数字如 [1, ‘1', 2, ‘2'] 时,此方法将产生错误结果。

reduce

鉴于此操作是对数组进行遍历并最终返回单一值,可考虑使用 reduce 方法简化代码:


// 方法3
var arr = [1, [2, [3, 4]]];
 
function flatten(arr) {
    return arr.reduce(function(prev, next){
        return prev.concat(Array.isArray(next) ? flatten(next) : next)
    }, [])
}
 
console.log(flatten(arr))

ES6 引入了扩展运算符,用于提取参数对象中所有可遍历的属性,并将其拷贝至当前对象:

var arr = [1, [2, [3, 4]]]; console.log([].concat(…arr)); // [1, 2, [3, 4]]

利用该方法仅能实现单层扁平化,但基于此思路深入推导,可构建如下处理逻辑:


var arr = [1, [2, [3, 4]]];
 
    function flatten(arr) {
     
        while (arr.some(item => Array.isArray(item))) {
            arr = [].concat(...arr);
        }
     
        return arr;
    }
     
    console.log(flatten(arr))

undercore

如何编写一个抽象的扁平化函数以简化开发流程?接下来我们将参考 underscore 库的实现。

此处直接提供源码及注释,需注意,这里的 flatten 函数并非最终的 _.flatten 版本,为了支持多个API调用,该函数增加了更多配置选项。


	/**
	 * 数组扁平化
	 * @param  {Array} input   要处理的数组
	 * @param  {boolean} shallow 是否只扁平一层
	 * @param  {boolean} strict  是否严格处理元素,下面有解释
	 * @param  {Array} output  这是为了方便递归而传递的参数
	 */


	function flatten(input, shallow, strict, output) {

    // 递归使用的时候会用到output
    output = output || [];
    var idx = output.length;

    for (var i = 0, len = input.length; i < len; i++) {

        var value = input[i];
        // 如果是数组,就进行处理
        if (Array.isArray(value)) {
            // 如果是只扁平一层,遍历该数组,依此填入 output
            if (shallow) {
                var j = 0, len = value.length;
                while (j < len) output[idx++] = value[j++];
            }
            // 如果是全部扁平就递归,传入已经处理的 output,递归中接着处理 output
            else {
                flatten(value, shallow, strict, output);
                idx = output.length;
            }
        }
        // 不是数组,根据 strict 的值判断是跳过不处理还是放入 output
        else if (!strict){
            output[idx++] = value;
        }
    }

    return output;

}

解释 strict 参数:在代码遍历数组元素时,若元素非数组,则判断 strict 取反的结果。若设置 strict 为 true,则跳过该元素不进行处理,这意味着可以过滤掉非数组类型的元素,示例如下:

var arr = [1, 2, [3, 4]]; console.log(flatten(arr, true, true)); // [3, 4]

设置 strict 参数的具体作用如何?先观察 shallow 和 strict 不同组合下的结果:

shallow true + strict false :正常扁平一层

shallow false + strict false :正常扁平所有层

shallow true + strict true :去掉非数组元素

shallow false + strict true : 返回一个[]

查看 underscore 中哪些方法调用了这个基础 flatten 函数:

_.flatten

首先是 _.flatten 的实现:


_.flatten = function(array, shallow) {
    return flatten(array, shallow, false);
};

在常规扁平化场景中,我们无需过滤非数组元素。

_.union

该函数接收多个数组作为参数,并返回这些数组的并集。

示例如下:

_.union([1, 2, 3], [101, 2, 1, 10], [2, 1]); => [1, 2, 3, 101, 10]

若传入的参数非数组,则该参数将被跳过:

_.union([1, 2, 3], [101, 2, 1, 10], 4, 5); => [1, 2, 3, 101, 10]

为实现此效果,可将所有传入数组进行扁平化后去重。由于仅接受数组参数,此时直接设置 strict 为 true,即可跳过非数组元素。


function unique(array) {
   return Array.from(new Set(array));
}
 
_.union = function() {
    return unique(flatten(arguments, true, true));
}

_.difference

看来 strict 参数确实有用,再看一个 _.difference 的例子:

语法为:

_.difference(array, *others)

其作用是提取存在于 array 数组中,但不存在于其他多个 other 数组中的元素。与 _.union 类似,它也会排除非数组元素。

示例如下:

_.difference([1, 2, 3, 4, 5], [5, 2, 10], [4], 3); => [1, 3]

实现方法简单,即对 others 数组进行扁平化,筛选出 array 中不在扁平化数组中的值:


function difference(array, ...rest) {
 
    rest = flatten(rest, true, true);
 
    return array.filter(function(item){
        return rest.indexOf(item) === -1;
    })
}

如果对您有所帮助,欢迎您点个关注,我会定时更新技术文档,大家一起讨论学习,一起进步。

总结

本篇文章就到这里了,希望能够给你带来帮助,也希望您能够多多关注本站的更多内容!

实用工具推荐

电子包浆

绿化、压缩、噪点和虚拟水印,自由调节做旧程度。

使用 ›
图片压缩

选择画质和输出格式,预览压缩前后的大小。

使用 ›
相关文章