lodash源码学习

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了lodash源码学习相关的知识,希望对你有一定的参考价值。

继续学习lodash,依然是数组的方法

“Array” Methods

_.indexOf(array, value, [fromIndex=0])

获取value在数组 array所在的索引值 使用 SameValueZero方式比较(第一个全等===的元素). 如果 fromIndex 值是负数, 则从array末尾起算

该方法依赖于strictIndexOf和baseIndexOf方法,先看它们的源码

//_strictIndexOf.js

/**
 * _.indexOf的专业版本,对元素执行严格的比较方法(===)
 *
 * @param {Array} array 需要处理的数组.
 * @param {*} value 查找的值.
 * @param {number} fromIndex 查找的开始位置.
 * @returns {number} 返回第一个匹配的值的索引,或者-1.
 */
function strictIndexOf(array, value, fromIndex) {
  var index = fromIndex - 1,//数值索引
      length = array.length;//数组长度

  while (++index < length) {//遍历,如果数组中的值和查找的值相同,返回对应的index
    if (array[index] === value) {
      return index;
    }
  }
  return -1;//返回-1
}

module.exports = strictIndexOf;
//_baseIndexOf.js

var baseFindIndex = require(‘./_baseFindIndex‘),//baseFindIndex方法(见源码学习(1))
    baseIsNaN = require(‘./_baseIsNaN‘),//判断是不是NaN
    strictIndexOf = require(‘./_strictIndexOf‘);//strictIndexOf方法

/**
 * _.indexOf的基本实现,不对fromIndex的范围进行检查
 *
 * @param {Array} array 需要处理的数组.
 * @param {*} value 需要查找的值.
 * @param {number} fromIndex 查找的开始位置.
 * @returns {number} 返回第一个匹配的值的索引,或者-1.
 */
function baseIndexOf(array, value, fromIndex) {
    //如果value === value(也就是不为NaN),调用strictIndexOf方法,否则调用baseFindIndex方法,判断条件为是否为NaN
    //并且将结果作为返回值返回
  return value === value 
    ? strictIndexOf(array, value, fromIndex)
    : baseFindIndex(array, baseIsNaN, fromIndex);
}

module.exports = baseIndexOf;

然后再看indexOf方法的源码

//indexOf.js

var baseIndexOf = require(‘./_baseIndexOf‘),//baseIndexOf方法
    toInteger = require(‘./toInteger‘);//转化为整形

var nativeMax = Math.max;

/**
 *
 *
 * @param {Array} array 需要处理的数组.
 * @param {*} value 需要查找的值.
 * @param {number} [fromIndex=0] 查找的开始位置.
 * @returns {number} 返回第一个匹配的值的索引,或者-1.
 * @example
 *
 * _.indexOf([1, 2, 1, 2], 2);
 * // => 1
 *
 * // Search from the `fromIndex`.
 * _.indexOf([1, 2, 1, 2], 2, 2);
 * // => 3
 */
function indexOf(array, value, fromIndex) {
  var length = array == null ? 0 : array.length;//数组长度
  if (!length) {//如果为空数组,返回-1
    return -1;
  }
  var index = fromIndex == null ? 0 : toInteger(fromIndex);//查找的起始位置,如果没有传入fromIndex则为0,否则为fromIndex
  if (index < 0) {//如果index为负值,从末尾算起
    index = nativeMax(length + index, 0);
  }
  return baseIndexOf(array, value, index);//调用baseIndexOf方法,并将结果作为返回值返回
}

module.exports = indexOf;

_.initial(array)

得到数组的除了最后一个元素以外的所有元素.

//initial.js

var baseSlice = require(‘./_baseSlice‘);//同Array.slice方法

/**
 * 
 *
 * @param {Array} array 需要处理的数组.
 * @returns {Array} 返回切好的数组.
 * @example
 *
 * _.initial([1, 2, 3]);
 * // => [1, 2]
 */
function initial(array) {
  var length = array == null ? 0 : array.length;//数组长度
  return length ? baseSlice(array, 0, -1) : [];//如果不为空数组,调用baseSlice方法并将结果返回,否则返回空数组
}

module.exports = initial;

_.intersection([arrays])

取出各数组中都有的元素,使用 SameValueZero方式比较(===).

_.intersectionBy([arrays], [iteratee=_.identity])

和_.intersection很像,除了它接受一个遍历器被每个元素调用,生成计算之后的属性当他们比较的时候。遍历器interatee只接收一个参数(value)

_.intersectionWith([arrays], [comparator])

和_.intersection很像,除了它接受一个比较规则被每个元素调用,比较方法接受两个参数(arrVal,othVal)

这三个方法依赖于baseIntersection方法

//_baseIntersection.js

var SetCache = require(‘./_SetCache‘),//Set缓存数组
    arrayIncludes = require(‘./_arrayIncludes‘),//同Array.includes方法
    arrayIncludesWith = require(‘./_arrayIncludesWith‘),//同Array.includes方法,除了他接受一个比较方法
    arrayMap = require(‘./_arrayMap‘),//同Array.map
    baseUnary = require(‘./_baseUnary‘),//创建一个只有一个参数的方法,忽略其他的参数
    cacheHas = require(‘./_cacheHas‘);//判断缓存中是否存在某个元素

var nativeMin = Math.min;

/**
 * _.intersection的基本实现, 不支持遍历器的简写.
 *
 * @private
 * @param {Array} arrays 需要处理的数组.
 * @param {Function} [iteratee] 遍历器,作用于每个元素.
 * @param {Function} [comparator] 比较器,作用于每个元素.
 * @returns {Array} 返回一个数组包含给定所有数组中共有的元素.
 */
function baseIntersection(arrays, iteratee, comparator) {
  var includes = comparator ? arrayIncludesWith : arrayIncludes,//如果有比较器,为arrayIncludesWith,否则为arrayIncludes
      length = arrays[0].length,//第一个数组的长度
      othLength = arrays.length,//给定的所有数组的长度
      othIndex = othLength,//给定所有数组的索引
      caches = Array(othLength),//创建一个缓存给定数组个数的数组
      maxLength = Infinity,//最大长度
      result = [];//返回结果
  //遍历给定的数组
  while (othIndex--) {
    var array = arrays[othIndex];//当前数组
    if (othIndex && iteratee) {//如果不是第一个数组,并且有遍历器,对当前数组调用遍历器遍历每个元素
      array = arrayMap(array, baseUnary(iteratee));//当前数组为调用之后的数组
    }
    maxLength = nativeMin(array.length, maxLength);//数组的最大长度
    //如果该数组长度超过120就创建Set缓存数组中并添加到caches中(用于优化)
    caches[othIndex] = !comparator && (iteratee || (length >= 120 && array.length >= 120))
      ? new SetCache(othIndex && array)
      : undefined;
  }
  array = arrays[0];//给定所有数组中的第一个

  var index = -1,//数组索引
      seen = caches[0];//第一个缓存数组,初始为undefined或者new SetCache(0)

  outer:
  //遍历第一个数组
  while (++index < length && result.length < maxLength) {
    var value = array[index],//数组元素
        computed = iteratee ? iteratee(value) : value;//如果有遍历器,对改元素调用,得到计算后的值

    value = (comparator || value !== 0) ? value : 0;//如果有比较器或者value不为0,value为value,否则为0
    if (!(seen//如果seen有值了,判断是否含有computed,否则判断结果中是否含有computed,如果都没有computed
          ? cacheHas(seen, computed)
          : includes(result, computed, comparator)
        )) {
      othIndex = othLength;//其他数组的索引,初始为给定数组的长度
      //遍历给定的数组,除了第一个
      while (--othIndex) {
        var cache = caches[othIndex];//缓存数组中的值
        //太长的数组就是cache,短的就是arrays[index],判断是否含有computed,如果没有,跳过此次循环
        if (!(cache
              ? cacheHas(cache, computed)
              : includes(arrays[othIndex], computed, comparator))
            ) {
          continue outer;
        }
      }
      if (seen) {//如果缓存存在,将computed添加到缓存中
        seen.push(computed);
      }
      result.push(value);//将value添加到结果数组中
    }
  }
  return result;//返回结果数组
}

module.exports = baseIntersection;

对应的方法在baseIntersection上进行处理

//intersection.js

var arrayMap = require(‘./_arrayMap‘),//同Array.map
    baseIntersection = require(‘./_baseIntersection‘),//baseIntersection方法
    baseRest = require(‘./_baseRest‘),//创建可以使用rest参数的方法
    castArrayLikeObject = require(‘./_castArrayLikeObject‘);//创建一个类似数组的对象,如果不是为空数组

/**
 * 
 * @param {...Array} [arrays] 需要处理的所有数组.
 * @returns {Array} 返回一个数组包含给定所有数组中共有的元素.
 * @example
 *
 * _.intersection([2, 1], [2, 3]);
 * // => [2]
 */
var intersection = baseRest(function(arrays) {//创建具备rest参数的方法
  var mapped = arrayMap(arrays, castArrayLikeObject);//将所有数组转化为类似数组的对象
  return (mapped.length && mapped[0] === arrays[0])//如果传入的数组个数不为0,并且第一个参数为数组,调用baseIntersection方法传入mapped,并将结果作为返回值返回,否则返回空数组
    ? baseIntersection(mapped)
    : [];
});

module.exports = intersection;
//intersectionBy.js

var arrayMap = require(‘./_arrayMap‘),//同Array.map
    baseIntersection = require(‘./_baseIntersection‘),//baseIntersection方法
    baseIteratee = require(‘./_baseIteratee‘),//遍历器封装
    baseRest = require(‘./_baseRest‘), //创建具有rest参数的方法
    castArrayLikeObject = require(‘./_castArrayLikeObject‘),//创建类似数组的对象
    last = require(‘./last‘);//取得数组的最后一个元素

/**
 * 
 * @param {...Array} [arrays] 需要处理的数组.
 * @param {Function} [iteratee=_.identity] 遍历器被每个元素调用.
 * @returns {Array} 返回一个数组包含给定所有数组中共有的元素.
 * @example
 *
 * _.intersectionBy([2.1, 1.2], [2.3, 3.4], Math.floor);
 * // => [2.1]
 *
 * // The `_.property` iteratee shorthand.
 * _.intersectionBy([{ ‘x‘: 1 }], [{ ‘x‘: 2 }, { ‘x‘: 1 }], ‘x‘);
 * // => [{ ‘x‘: 1 }]
 */
var intersectionBy = baseRest(function(arrays) {//创建具备rest参数的方法
  var iteratee = last(arrays),//遍历器为最后一个参数
      mapped = arrayMap(arrays, castArrayLikeObject);//将所有数组转化为类似数组的对象

  if (iteratee === last(mapped)) {//如果遍历器和mapped最后一个一样(也就是最后一个参数为数组)
    iteratee = undefined;//不存在遍历器
  } else {
    mapped.pop();//否则将mapped最后一个元素去掉(遍历器转化的元素)
  }
  return (mapped.length && mapped[0] === arrays[0])
  //如果传入的数组个数不为0,并且第一个参数为数组,调用baseIntersection方法传入mapped和iteratee,并且支持属性的简写,并将结果作为返回值返回,否则返回空数组
    ? baseIntersection(mapped, baseIteratee(iteratee, 2))
    : [];
});

module.exports = intersectionBy;
//intersectionWith.js

var arrayMap = require(‘./_arrayMap‘),//同Array.map
    baseIntersection = require(‘./_baseIntersection‘),//baseIntersection方法
    baseRest = require(‘./_baseRest‘),//创建具有rest参数的方法
    castArrayLikeObject = require(‘./_castArrayLikeObject‘),//创建类似数组的对象
    last = require(‘./last‘);//取得数组的最后一个元素

/**
 *
 * @param {...Array} [arrays] 需要处理的数组.
 * @param {Function} [comparator] 比较方法.
 * @returns {Array} 返回一个数组包含给定所有数组中共有的元素.
 * @example
 *
 * var objects = [{ ‘x‘: 1, ‘y‘: 2 }, { ‘x‘: 2, ‘y‘: 1 }];
 * var others = [{ ‘x‘: 1, ‘y‘: 1 }, { ‘x‘: 1, ‘y‘: 2 }];
 *
 * _.intersectionWith(objects, others, _.isEqual);
 * // => [{ ‘x‘: 1, ‘y‘: 2 }]
 */
var intersectionWith = baseRest(function(arrays) {//创建具有rest参数的方法
  var comparator = last(arrays),//比较器为最后一个元素
      mapped = arrayMap(arrays, castArrayLikeObject);//将所有数组转化为类似数组的对象

  comparator = typeof comparator == ‘function‘ ? comparator : undefined;//比较器如果不为function,则为undefined
  if (comparator) {//如果有比较器,将mapped最后一个元素去掉(比较器转化的元素)
    mapped.pop();
  }
  //如果传入的数组个数不为0,并且第一个参数为数组,调用baseIntersection方法传入mapped和comparator,并将结果作为返回值返回,否则返回空数组
  return (mapped.length && mapped[0] === arrays[0])
    ? baseIntersection(mapped, undefined, comparator)
    : [];
});

module.exports = intersectionWith;

_.join(array, [separator=‘,‘])

 转化数组为字符串,用指定的分割符将每个元素连接起来.

//join.js

var arrayProto = Array.prototype;//数组的原型

var nativeJoin = arrayProto.join;//原生join方法

/**
 *
 * @param {Array} array 需要转化的数组.
 * @param {string} [separator=‘,‘] 分割符.
 * @returns {string} 返回连接起来的字符串.
 * @example
 *
 * _.join([‘a‘, ‘b‘, ‘c‘], ‘~‘);
 * // => ‘a~b~c‘
 */
function join(array, separator) {
  return array == null ? ‘‘ : nativeJoin.call(array, separator);//如果数组为空,返回空字符串,否则调用原生的join方法,并将结果返回
}

module.exports = join;

_.last(array)

得到数组的最后一个元素

//last.js

/**
 *
 * @param {Array} array 需要处理的数组.
 * @returns {*} 返回数组的最后一个元素.
 * @example
 *
 * _.last([1, 2, 3]);
 * // => 3
 */
function last(array) {
  var length = array == null ? 0 : array.length;
  return length ? array[length - 1] : undefined;//不解释
}

module.exports = last;

_.lastIndexOf(array, value, [fromIndex=array.length-1])

和_.indexOf很像,但是是从右到左开始遍历

该方法依赖于strictLastIndexOf方法

//_strictLastIndexOf.js

/**
 * _.lastIndexOf的专业版本,对元素执行严格的比较方法(===).
 *
 * @param {Array} array 需要处理的数组.
 * @param {*} value 需要查找的值.
 * @param {number} fromIndex 查找的开始位置.
 * @returns {number} 返回第一个匹配的元素的索引,或者-1.
 */
function strictLastIndexOf(array, value, fromIndex) {
  var index = fromIndex + 1;//数值索引
  while (index--) {
    if (array[index] === value) {//遍历,如果数组中的值和查找的值相同,返回对应的index
      return index;
    }
  }
  return index;//返回index(如果没有找到,index的值为-1)
}

module.exports = strictLastIndexOf;

再看lastIndexOf方法的源码

//lastIndexOf.js

var baseFindIndex = require(‘./_baseFindIndex‘),//baseFindIndex方法(见源码学习(1))
    baseIsNaN = require(‘./_baseIsNaN‘),//判断是否为NaN
    strictLastIndexOf = require(‘./_strictLastIndexOf‘),//strictLastIndexOf方法
    toInteger = require(‘./toInteger‘);//转化为整型

var nativeMax = Math.max,//原生最大值方法
    nativeMin = Math.min;//原生最小值方法

/**
 *
 * @param {Array} array 需要处理的数组.
 * @param {*} value 需要查找的值.
 * @param {number} [fromIndex=array.length-1] 查找的开始位置.
 * @returns {number} 返回匹配的第一个元素的索引,或者-1.
 * @example
 *
 * _.lastIndexOf([1, 2, 1, 2], 2);
 * // => 3
 *
 * // Search from the `fromIndex`.
 * _.lastIndexOf([1, 2, 1, 2], 2, 2);
 * // => 1
 */
function lastIndexOf(array, value, fromIndex) {
  var length = array == null ? 0 : array.length;//数组长度
  if (!length) {//如果为空数组,返回-1
    return -1;
  }
  var index = length;//数组索引
  if (fromIndex !== undefined) {//如果有起始位置
    index = toInteger(fromIndex);//转换为整形
    index = index < 0 ? nativeMax(length + index, 0) : nativeMin(index, length - 1);//判断起始位置,在0到length-1之间,如果为负,从末尾算起
  }
  return value === value//如果不是NaN,调用strictLastIndexOf方法,否则调用baseFindIndex方法,并且传入判断条件为baseIsNaN
    ? strictLastIndexOf(array, value, index)
    : baseFindIndex(array, baseIsNaN, index, true);
}

module.exports = lastIndexOf;

_.nth(array, [n=0])

得到数组指定索引的值,如果索引为负值,从数组末尾算起.

该方法依赖于baseNth方法

//_baseNth.js

var isIndex = require(‘./_isIndex‘);//是否为正确的索引

/**
 * _.nth的基本实现,不强制参数
 *
 * @private
 * @param {Array} array 需要查询的数组
 * @param {number} n 查找元素的index.
 * @returns {*} 返回查找到的元素.
 */
function baseNth(array, n) {
  var length = array.length;//数组长度
  if (!length) {//如果为空数组,返回空数组
    return;
  }
  n += n < 0 ? length : 0;//n<0从末尾算,否则就是n
  return isIndex(n, length) ? array[n] : undefined;//如果是正确的索引,返回索引对应的值,否则返回undefined
}

module.exports = baseNth;

再看nth.js

//nth.js

var baseNth = require(‘./_baseNth‘),//baseNth方法
    toInteger = require(‘./toInteger‘);//转化为整形

/**
 *
 * @param {Array} array 需要查询的数组.
 * @param {number} [n=0] 查找元素的index..
 * @returns {*} 返回查找到的元素.
 * @example
 *
 * var array = [‘a‘, ‘b‘, ‘c‘, ‘d‘];
 *
 * _.nth(array, 1);
 * // => ‘b‘
 *
 * _.nth(array, -2);
 * // => ‘c‘;
 */
function nth(array, n) {
  return (array && array.length) ? baseNth(array, toInteger(n)) : undefined;//不解释
}

module.exports = nth;

_.pull(array, [values])

移除数组array中所有和 values 相等的元素,使用 SameValueZero 进行全等比较.

_.pullAll(array, values)

和_.pull很像除了它接受一个需要移除的元素的数组

_.pullAllBy(array, values, [iteratee=_.identity])

这个方法和_.pullAll很像除了它接受一个遍历方法被每个元素调用,遍历方法接受一个参数(value)

_.pullAllWith(array, values, [comparator])

这4个方法都依赖于basePullAll方法

//_basePullAll.js

var arrayMap = require(‘./_arrayMap‘),//同Array.map
    baseIndexOf = require(‘./_baseIndexOf‘),//同Array.indexOf,上面已分析
    baseIndexOfWith = require(‘./_baseIndexOfWith‘),//类似baseIndexOf,除了接受一个比较方法
    baseUnary = require(‘./_baseUnary‘),//创建一个只有一个参数的方法,忽略其他的参数
    copyArray = require(‘./_copyArray‘);//拷贝数组

var arrayProto = Array.prototype;//数组原形

var splice = arrayProto.splice;//原生splice方法

/**
 * _.pullAllBy的基本实现,不支持遍历器的简写
 *
 * @private
 * @param {Array} array 需要修改的数组.
 * @param {Array} values 需要移除的元素.
 * @param {Function} [iteratee] 遍历器,对每个元素调用.
 * @param {Function} [comparator] 比较器,对每个元素调用.
 * @returns {Array} 返回修改之后的数组.
 */
function basePullAll(array, values, iteratee, comparator) {
  var indexOf = comparator ? baseIndexOfWith : baseIndexOf,//选择indexOf方法,如果传入比较器为baseIndexOfWith,否则为baseIndexOf
      index = -1,//数值索引
      length = values.length,//数组长度
      seen = array;//获取一个array的引用

  if (array === values) {//如果需要移除的元素和需要修改的数组一样,拷贝一份values
    values = copyArray(values);
  }
  if (iteratee) {//如过存在遍历器,对数组进行遍历操作
    seen = arrayMap(array, baseUnary(iteratee));
  }
  //遍历需要移除的数组
  while (++index < length) {
    var fromIndex = 0,//元素在数组中的开始位置
        value = values[index],//需要移除的元素
        computed = iteratee ? iteratee(value) : value;//如果有遍历器对value进行调用,得到 计算后的value(也就是computed)
    //循环执行indexOf方法,如果seen中,直到找不到computed为止
    while ((fromIndex = indexOf(seen, computed, fromIndex, comparator)) > -1) {
      if (seen !== array) {//seen和array不相等即存在遍历器,对seen执行splice方法移除fromIndex的元素
        splice.call(seen, fromIndex, 1);
      }
      //对array执行splice方法移除fromIndex的元素
      splice.call(array, fromIndex, 1);
    }
  }
  return array;//返回修改后的array
}

module.exports = basePullAll;

相对应的方法的源码

//pullAll.js

var basePullAll = require(‘./_basePullAll‘);//basePullAll方法

/**
 *
 * @param {Array} array 需要修改的数组.
 * @param {Array} values 需要移除的值.
 * @returns {Array} 返回修改后的数组.
 * @example
 *
 * var array = [‘a‘, ‘b‘, ‘c‘, ‘a‘, ‘b‘, ‘c‘];
 *
 * _.pullAll(array, [‘a‘, ‘c‘]);
 * console.log(array);
 * // => [‘b‘, ‘b‘]
 */
function pullAll(array, values) {
  return (array && array.length && values && values.length)
    ? basePullAll(array, values)
    : array;//不解释
}

module.exports = pullAll;
//pull.js

var baseRest = require(‘./_baseRest‘),//创建具有rest参数的方法
    pullAll = require(‘./pullAll‘);

/**
 *
 * @param {Array} array 需要修改的数组.
 * @param {...*} [values] The values to remove.
 * @returns {Array} Returns `array`.
 * @example
 *
 * var array = [‘a‘, ‘b‘, ‘c‘, ‘a‘, ‘b‘, ‘c‘];
 *
 * _.pull(array, ‘a‘, ‘c‘);
 * console.log(array);
 * // => [‘b‘, ‘b‘]
 */
var pull = baseRest(pullAll);//将pullAll转换成可以使用rest参数的方法(pull(array,...values))

module.exports = pull;
//pullAllBy.js

var baseIteratee = require(‘./_baseIteratee‘),//封装遍历器
    basePullAll = require(‘./_basePullAll‘);//basePullAll方法

/**
 *
 * @param {Array} array 需要修改的数组.
 * @param {Array} values 需要移除的值.
 * @param {Function} [iteratee=_.identity] 遍历器,被每个元素调用.
 * @returns {Array} 返回修改后的数组.
 * @example
 *
 * var array = [{ ‘x‘: 1 }, { ‘x‘: 2 }, { ‘x‘: 3 }, { ‘x‘: 1 }];
 *
 * _.pullAllBy(array, [{ ‘x‘: 1 }, { ‘x‘: 3 }], ‘x‘);
 * console.log(array);
 * // => [{ ‘x‘: 2 }]
 */
function pullAllBy(array, values, iteratee) {
  return (array && array.length && values && values.length)
    ? basePullAll(array, values, baseIteratee(iteratee, 2))
    : array;//不解释
}

module.exports = pullAllBy;
//pullWidth.js

var basePullAll = require(‘./_basePullAll‘);//basePullAll方法

/**
 *
 * @param {Array} array 需要修改的数组.
 * @param {Array} values 需要移除的元素.
 * @param {Function} [comparator] 比较器被每个元素调用.
 * @returns {Array} 返回修改后的数组.
 * @example
 *
 * var array = [{ ‘x‘: 1, ‘y‘: 2 }, { ‘x‘: 3, ‘y‘: 4 }, { ‘x‘: 5, ‘y‘: 6 }];
 *
 * _.pullAllWith(array, [{ ‘x‘: 3, ‘y‘: 4 }], _.isEqual);
 * console.log(array);
 * // => [{ ‘x‘: 1, ‘y‘: 2 }, { ‘x‘: 5, ‘y‘: 6 }]
 */
function pullAllWith(array, values, comparator) {
  return (array && array.length && values && values.length)
    ? basePullAll(array, values, undefined, comparator)
    : array;//不解释
}

module.exports = pullAllWith;

一天一点进步,一步一个脚印~~~

以上是关于lodash源码学习的主要内容,如果未能解决你的问题,请参考以下文章

lodash源码学习(13)

lodash源码学习

lodash源码学习

lodash源码学习

lodash源码学习(10)

lodash源码学习debounce,throttle