1.源码
function reduce(collection, iteratee, accumulator) {
const func = Array.isArray(collection) ? arrayReduce : baseReduce
const initAccum = arguments.length < 3
return func(collection, iteratee, accumulator, initAccum, baseEach)
}
function arrayReduce(array, iteratee, accumulator, initAccum) {
let index = -1
const length = array == null ? 0 : array.length
if (initAccum && length) {
accumulator = array[++index]
}
while (++index < length) {
accumulator = iteratee(accumulator, array[index], index, array)
}
return accumulator
}
function baseReduce(collection, iteratee, accumulator, initAccum, eachFunc) {
eachFunc(collection, (value, index, collection) => {
accumulator = initAccum
? (initAccum = false, value)
: iteratee(accumulator, value, index, collection)
})
return accumulator
}
function baseEach(collection, iteratee) {
if (collection == null) {
return collection
}
if (!isArrayLike(collection)) {
return baseForOwn(collection, iteratee)
}
const length = collection.length
const iterable = Object(collection)
let index = -1
while (++index < length) {
if (iteratee(iterable[index], index, iterable) === false) {
break
}
}
return collection
}
2.测试
reduce([1, 2], (sum, n) => sum + n, 0)
// => 3
reduce({ 'a': 1, 'b': 2, 'c': 1 }, (w) => {
(result[value] || (result[value] = [])).push(key)
return result
}, {})
// => { '1': ['a', 'c'], '2': ['b'] }
3.理解
首先看看函数入口
function reduce(collection, iteratee, accumulator) {
const func = Array.isArray(collection) ? arrayReduce : baseReduce
const initAccum = arguments.length < 3
return func(collection, iteratee, accumulator, initAccum, baseEach)
}
- collection : 作用的数组或者对象
- iteratee : 遍历方法,作用于数组和作用于对象有所不同
- accumulator : 初始值,可选,如果不传入的话函数会赋一个默认值,数组和对象有所不同
2.1 当作用于数组
function arrayReduce(array, iteratee, accumulator, initAccum) {
let index = -1
const length = array == null ? 0 : array.length
if (initAccum && length) {
accumulator = array[++index]
}
while (++index < length) {
accumulator = iteratee(accumulator, array[index], index, array)
}
return accumulator
}
作用于数组时,首先来判断数组是否为空,为空的话不会进入if判断,也不会走while循环,将直接返回初始值。
当不为空时,如果没有给初始值,初始值为数组首项,接着通过传入的遍历函数遍历数组,函数可以接受4个参数,上次遍历的结果,本此遍历的值,本次遍历的索引,以及数组本身,并且返回下次遍历的初始值。
2.2 当作用于对象
function baseReduce(collection, iteratee, accumulator, initAccum, eachFunc) {
eachFunc(collection, (value, index, collection) => {
accumulator = initAccum
? ((initAccum = false), value)
: iteratee(accumulator, value, index, collection);
});
return accumulator;
}
当作用于对象的时候,情况稍微复杂一点,lodash在我们传入的遍历函数的基础上又调用了函数eachFunc,在eachFunc的回调函数中,先判断有没有初始值,如果没有就是对象第一个属性的value,接下来再调用传入的遍历函数,接下来看看baseEach。
function baseEach(collection, iteratee) {
if (collection == null) {
return collection
}
if (!isArrayLike(collection)) {
return baseForOwn(collection, iteratee)
}
const length = collection.length
const iterable = Object(collection)
let index = -1
while (++index < length) {
if (iteratee(iterable[index], index, iterable) === false) {
break
}
}
return collection
}
在baseEach方法中,做了一个判断,来看看这个isArrayLike方法
function isArrayLike(value) {
return value != null && typeof value !== "function" && isLength(value.length);
}
function isLength(value) {
return (
typeof value === "number" &&
value > -1 &&
value % 1 == 0 &&
value <= MAX_SAFE_INTEGER
);
}
这个方法简单说来就是通过判断对象有没有length属性,进而判断它是否有数组特性,即可以通过下标访问,比如:
- 字符串
- document.body.children
- arguments对象
这里再说一下类数组
什么是类数组?
类数组的本质是一个Object,当它的key值是索引(数字),并且具有length属性,就可以用访问数组的方式访问它,比如arguments就是一个典型的类数组。
类数组会同时具有对象和数组的特性,使用起来会非常方便。比如:
这里我们只是调用了一次push方法,但是却改变了它的length值
为什么要排除function的情况呢?
因为函数是有length属性的,它指该函数有多少个必须要传入的参数
好,接着往下看,当判断作用的对象不是类数组时,函数调用了另一个方法baseForOwn。
function baseForOwn(object, iteratee) {
return object && baseFor(object, iteratee, keys);
}
function keys(object) {
return isArrayLike(object)
? arrayLikeKeys(object)
: Object.keys(Object(object))
}
function baseFor(object, iteratee, keysFunc) {
const iterable = Object(object);
const props = keysFunc(object);
let { length } = props;
let index = -1;
while (length--) {
const key = props[++index];
if (iteratee(iterable[key], key, iterable) === false) {
break;
}
}
return object;
}
第一步依然是判断数组是否为空,不为空调用baseFor方法,这里的keys方法目的是取对象的key值集合,如果是具有数组特性则使用arrayLikeKeys方法,如果不是就调用Object.keys。
取到了之后传入遍历函数进行遍历,这里传入3个参数,每一项的value,key,对象本身,直到为空推出循环,最后返回对象,注意这里的遍历函数还不是我们写的遍历函数,还是封装过一层的函数,传递进去才会运行我们写的回调函数。
如果有数组特性的话,操作就会简单很多,只需要按照遍历数组的方式进行遍历。if里面的写法,是遍历函数每次执行之后会将返回值和false相比较,当index溢出,传入的值为null的时候遍历函数会返回null,进而跳出循环。
const length = collection.length
const iterable = Object(collection)
let index = -1
while (++index < length) {
if (iteratee(iterable[index], index, iterable) === false) {
break
}
}
最后
贴上两个关于reduce的面试题
1,使用map方法,实现reduce方法
Array.prototype.myReduce = function(callback,init){
let arr = Array.prototype.slice.call(this);
arr.map((item,index,array) => {
init = callback(init,item,index,array);
});
return init;
}
var arr = [1,2,3,4,5];
var sum = arr.myReduce((pre,item) => pre+item , 0);
console.log(sum); //15
2,使用reduce方法,实现map方法
Array.prototype.myMap = function(callback){
let arr = Array.prototype.slice.call(this);
arr = arr.reduce((pre,next,index,array) => {
var next_new = callback(next,index,array);
pre.push(next_new);
return pre;
},[])
return arr;
}
var arr = [1,2,3,4,5];
arr = arr.myMap(item => item * 2);
console.log(arr); //15