lodash源码阅读1-----------reduce

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
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值