difference方法

功能

创建一个具有唯一array值的数组,每个值不包含在其他给定的数组中。

使用方法

_.difference([3, 2, 1], [4, 2]);
// => [3, 1]

源码分析

var difference = baseRest(function(array, values) {
  return isArrayLikeObject(array)
    ? baseDifference(array, baseFlatten(values, 1, isArrayLikeObject, true))
    : [];
});

该方法用到了baseReset方法。它接收的参数是一个匿名函数,匿名函数内接收两个参数,一个是array数组,另一个就是defference的两个参数

/**
 * The base implementation of `_.rest` which doesn't validate or coerce arguments.
 * _.reset的基本实现,但是不会校验或者corce参数。
 * @private
 * @param {Function} func The function to apply a rest parameter to.
 * @param {number} [start=func.length-1] The start position of the rest parameter.
 * @returns {Function} Returns the new function.
 */
function baseRest(func, start) { // start = undefined
  start = nativeMax(start === undefined ? (func.length - 1) : start, 0); // var nativeMax = Math.max;

  return function() {
    var args = arguments,
        index = -1,
        length = nativeMax(args.length - start, 0),
        array = Array(length);

    while (++index < length) {
      array[index] = args[start + index];
    }
    index = -1;
    var otherArgs = Array(start + 1);
    while (++index < start) {
      otherArgs[index] = args[index];
    }
    otherArgs[start] = array;
    return apply(func, this, otherArgs);
  };
}

baseReset_.rest方法(创建一个函数,调用func时,this绑定到创建的新函数,并且start之后的参数作为数组传入。)的基础实现。

看一下它执行的效果。

var say = _.rest(function(what, names) {
  return what + ' ' + _.initial(names).join(', ') +
    (_.size(names) > 1 ? ', & ' : '') + _.last(names);
});
 
say('hello', 'fred', 'barney', 'pebbles');
// => 'hello fred, barney, & pebbles'

_.rest,传入一个匿名函数,返回一个新函数,并赋值给了say,say('hello', 'fred', 'barney', 'pebbles'),其中hello对应的是what,剩余的参数对应的是names

再回到baseReset.

  • step1 start这里,如果没传入start则start就是0
  • step2 baseReset执行后,返回了一个函数,由于闭包,又保证了func,和start的访问。

(往上看好烦啊,我copy了返回的函数到下边)

// func函数 ,start=0
return  function() { // arguments 就像上文say函数调用时传入的参数,假设此时传入了6个参数,[a,b,c,d,e,f]
    var args = arguments,//args.length = 6  
        index = -1,
        length = nativeMax(args.length - start, 0),//6
        array = Array(length);// array为长度为6的数组,

    while (++index < length) { //   0,1,2,3,4,5 <6
      array[index] = args[start + index]; // arr[0] = args[0+0],arr[1]= args[0+1]
    }
    // finally  6个长度的数组`array` array = [a,b,c,d,e,f]
    index = -1; // 重置index = -1
    var otherArgs = Array(start + 1); // otherArgs 为一个长度的数组 [] length = 1
    while (++index < start) {
      otherArgs[index] = args[index];// otherArgs  = 取出args[0]
    }
    
   
    otherArgs[start] = array;// otherArgs[0] = [a,b,c,d,e,f]
    return apply(func, this, otherArgs); // apply执行
  };

此处apply比原生的apply速度更快,作用跟原生js的apply用法和作用是一样的。

function apply(func, thisArg, args) {
  switch (args.length) {
    case 0: return func.call(thisArg);
    case 1: return func.call(thisArg, args[0]);
    case 2: return func.call(thisArg, args[0], args[1]);
    case 3: return func.call(thisArg, args[0], args[1], args[2]);
  }
  return func.apply(thisArg, args);
}

注意: while中的i++和 ++i的区别,++i是先算值再判断符合条件,i++则是先判断是否符合条件,再自增。

difference的关键代码就是这段.

baseDifference(array, baseFlatten(values, 1, isArrayLikeObject, true))

接下来自然要看baseDifference,baseFlatten.

baseFlatten concat一篇中已经出现过了,是用来打平多维数组。

接下俩看baseDifference

**
 * -.difference的基本实现。
 *
 * @private
 * @param {Array} array The array to inspect. 需要被检查的数组
 * @param {Array} values The values to exclude. 需要被排除出去的数组
 * @param {Function} [iteratee] The iteratee invoked per element. 迭代器函数,调用每个element
 * @param {Function} [comparator] The comparator invoked per element. 比较器,也会调用每个ele
 * @returns {Array} Returns the new array of filtered values. 返回被过滤调的新的数组
 */
function baseDifference(array, values, iteratee, comparator) {
  var index = -1,
      includes = arrayIncludes,//方法。
      isCommon = true,
      length = array.length,
      result = [],
      valuesLength = values.length;

  if (!length) {
    return result;
  }
  if (iteratee) { // 迭代器函数。 这里需要研究arrayMap 和baseUnary方法了

    values = arrayMap(values, baseUnary(iteratee)); // 这里提前处理下  while循环里边有个判段,这里提前就遍历,处理数据。
  }
  if (comparator) { //comparator存在,includes从arrayIncludes =》arrayIncludesWith,
    includes = arrayIncludesWith;
    isCommon = false;
  }
  else if (values.length >= LARGE_ARRAY_SIZE) {
    //- 大型数组的优化,这里默认理解为超过200就是大数组。大的数组启用缓存。
    includes = cacheHas; // includes方法设置为cacheHas处理,这里也是做缓存
    isCommon = false;//标示  不是普通方式处理了
    values = new SetCache(values);
  }
  outer:
   //切记,比对的是array,values
  while (++index < length) {
    var value = array[index],//array的一个element
        computed = iteratee ? iteratee(value) : value;//如果有迭代器,就处理成为computed

    value = (comparator || value !== 0) ? value : 0;
    if (isCommon && computed === computed) { // 取出来的数据不是NaN
      var valuesIndex = valuesLength;
      while (valuesIndex--) {
        if (values[valuesIndex] === computed) {
          continue outer; //跳会outer,继续执行  因为是求差集,也就是value中的元素,在array不能存在。 这里有相同,array中的当前元素就不应该在结果集里出现。
        }
      }
      result.push(value);
    }
    else if (!includes(values, computed, comparator)) {
      result.push(value);
    }
  }
  return result;
}

普通的处理方式相对好处理一些。isCommon用来标示是否使用普通处理。为了做优化,作者显然做了更多。有两种情况需要做优化

  • comparator函数传入,此时incluedes=arrayIncludesWith
  • values数组太大。此时incluedes=cacheHas ,`values = new SetCache(values);
 else if (!includes(values, computed, comparator)) {
      result.push(value);
    }

非正常情况下做的处理代码。

这块详细说,代码就太长了再开一篇吧。

相关方法分析

arrayIncludes

/**
 * 数组中Array是否包含 value ,
 * @private
 * @param {Array} [array] The array to inspect.
 * @param {*} target The value to search for.
 * @returns {boolean} Returns `true` if `target` is found, else `false`.
 */
function arrayIncludes(array, value) {
  var length = array ? array.length : 0;
  return !!length && baseIndexOf(array, value, 0) > -1;
}
// arrayIncludes(["123"],"123") => true

baseIndexOf


/**
 * The base implementation of `_.indexOf` without `fromIndex` bounds checks.
 * _.indexOf的基本实现,没有fromIndex的边界检查
 * @private
 * @param {Array} array The array to inspect. 被检查的数组
 * @param {*} value The value to search for.  被查找的值
 * @param {number} fromIndex The index to search from. 从哪个位置开始search
 * @returns {number} Returns the index of the matched value, else `-1`.
 */
function baseIndexOf(array, value, fromIndex) {
  if (value !== value) { //NaN就调用baseFindIndex
    return baseFindIndex(array, baseIsNaN, fromIndex);
  }
  var index = fromIndex - 1, // -1
      length = array.length; //

  while (++index < length) {
    if (array[index] === value) {
      return index; //遍历判断结果,有符合条件 返回index,否则为-1
    }
  } 
  return -1;
}

arrayMap

/**
 * A specialized version of `_.map` for arrays without support for iteratee
 * shorthands.
 *
 * @private
 * @param {Array} [array] The array to iterate over. 用来被遍历的数组
 * @param {Function} iteratee The function invoked per iteration. 迭代器
 * @returns {Array} Returns the new mapped array. 
 */
function arrayMap(array, iteratee) {
  var index = -1,
      length = array ? array.length : 0,
      result = Array(length); //结果数组

  while (++index < length) {
    result[index] = iteratee(array[index], index, array); // 这里就遍历了数据,返回遍历的结果。
  }
  return result;
}

baseUnary

/**
 * 创建一个最多接受一个参数的函数,忽略多余的参数。
 *
 * @private  
 * @param {Function} func The function to cap arguments for.
 * @returns {Function} Returns the new capped function.
 */
function baseUnary(func) {
  return function(value) {
    return func(value);
  };
}

最普通的一个
301 声望41 粉丝

永远不要做你擅长的事。


引用和评论

0 条评论