JavaScript数组扁平化实现方案与Underscore源码解析
本文详细解析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;
})
}
如果对您有所帮助,欢迎您点个关注,我会定时更新技术文档,大家一起讨论学习,一起进步。
总结
本篇文章就到这里了,希望能够给你带来帮助,也希望您能够多多关注本站的更多内容!
JavaScript怎么准确判断数据类型:typeof、instanceof与Object.prototype.toString
2026-11-05 17:09:21
JS中将数组和对象混合组装为JSON字符串
2026-11-05 16:41:42
JS运算符优先级与结合性的区别是什么?
2026-11-05 13:22:02
JS Map怎么只遍历Key或Value
2026-11-05 13:00:36
D3.js 与 ECharts 选型对比:灵活性 vs 开发效率
2026-11-05 09:45:01
