我们先来看一个题目 :
完成一个flatten数组,实现排平一个js的多维数组为一维,示例如下
var testArr1 = [[0, 1], [2, 3], [4, 5]];
var testArr2 = [0, [1, [2, [3, [4,[5]]]]]];
flatten(testArr1) // [0, 1, 2, 3, 4, 5];
flatten(testArr2) // [0, 1, 2, 3, 4, 5];
递归
第一个想到的念头肯定是递归,递归自然就想到递归的尽头,那就是判断数组某项元素是否还是数组类型
var flatten = function(array){
return array.reduce(function(previous, val){
if(Object.prototype.toString.call(val) !== '[object Array]'){
return (previous.push(val), previous);
}
return (previous.push(flatten(val)), previous);
}, []);
}
我们先来逐步拆分下
return的是什么
我们注意到上面的写法return使用了括号表达式,括号内容的前半部分是为了执行
function multiply2(){
var a = 1;
return (a = a * 2, a);
}
multiply2(); // 2
Object.prototype.toString.call
Object.prototype.toString.call在这里只是用来判断是否是数组,在ES5中的isArray
方法的实现就是这样,可以参考另一篇博文js数组应用
function isArray(o) {
// 利用参数的toString方法
return Object.prototype.toString.call(o) === '[object Array]';
}
reduce方法
reduce方法是ES5引入,使用的场景并不多,但是了解它的特性确实必须的。reduce方法在MDN中的直接描述为: The reduce method applies a function against an accumulator and each value of the array(from left-to-right) to reduce it to a single value。我们来看它的具体使用
var values = [1, 2, 3, 4, 5];
var sum = values.reduce(function(prev, cur, index, array){
return prev + cur;
})
alert(sum);
reduce函数接受4个参数: 前一个值,当前值,项的索引和数组对象。这个函数返回的任何值都会做为第一个参数自动传给下一项
分析刚才的函数
结合上面分析,我们就得到了函数的另一个样子。原本我们会使用另一个数组来保存拍平的数组结果,结果reduce函数直接在每次迭代中保存了一个数组。
var flatten = function(array){
return array.reduce(function(previous, val){
if(!Array.isArray(array)){
//每次previous数组保存了我们每次遍历得到的新结果
return (previous.push(val), previous);
}
//如果是数组,继续递归使用拍平函数,最后还是要返回previous的引用
return (previous.push(flatten(val)), previous);
//这里的数组[]为previous的初始值
}, []);
}
这个方法写成ES6就是如下形式
const flatten = array => array.reduce((pre, val)=> pre.concat(!Array.isArray(array) ? val : flatten(val)),[]);
如何实现一个reduce的pollyfill
现在明白了reduce的秘密,接下来我们需要充分发挥对JS的理解,来手动实现一个reduce函数。毕竟,reduce是ES5带来的数组新特性,在不使用ES5-shim的情况下,需要手动兼容。第一种是译者直接用循环写的,第二种直接使用数组的现成方法。
// 译者方法
var reduce = function(array, fn, initialvalue){
var prev = typeof initialvalue == undefined ? array[0] : initialvalue;
var startPoint = initialvalue ? 1 : 0;
for(var i = startPoint,len = array.length; i < len; i++){
prev = fn(prev, array[i], i, array);
}
return prev;
}
var reduce = function(arr, func, initialValue) {
var base = typeof initialValue === 'undefined' ? arr[0] : initialValue;
var startPoint = typeof initialValue === 'undefined' ? 1 : 0;
arr.slice(startPoint)
.forEach(function(val, index) {
base = func(base, val, index + startPoint, arr);
});
return base;
};
译者这里提醒,如果Array.slice
只传入一个参数n的时候,是直接切除n个元素,并一样返回新数组索引,所以可以进行链式调用
var array = [1, 2, 3];
var newArray = array.slice(2); // [3]
ES5-shim的pollyfill
ES5-shim里的pollyfill,跟上面译者方法基本完全一致。
Redux中的reducer
熟悉Redux数据流架构的同学理解reducer做了什么,关于这个纯函数的命名,在redux源码github仓库上也有一个官方解释:“It’s called a reducer because it’s the type of function you would pass to Array.prototype.reduce(reducer, ?initialValue)”,虽然是一笔带过,但是总结的恰到好处。