首发于听你听我
Underscore源码分析系列(3)

Underscore源码分析系列(3)

目录

Underscore源码分析系列(1) - Liril的文章 - 知乎专栏

Underscore源码分析系列(2) - Liril的文章 - 知乎专栏

Underscore源码分析系列(3) - Liril的文章 - 知乎专栏

Underscore源码分析系列(4) - Liril的文章 - 知乎专栏

Underscore源码分析系列(5) - Liril的文章 - 知乎专栏

Underscore源码分析系列(6) - Liril的文章 - 知乎专栏



说明

这一部分是对数组函数进行分析。



_.initial

// 返回数组除了最后n个外的其他元素
// 调用了slice方法,如果没有传递n或者传递了guard则返回除了最后一个外的元素
_.initial = function(array, n, guard) {
return slice.call(array, 0, Math.max(0, array.length - (n == null || guard ? 1 : n)));
};

_.first、_.head、_.take

获取数组的前n个元素,如果没有n或者传入了guard则为第一个元素。

_.first = _.head = _.take = function(array, n, guard) {
if (array == null || array.length < 1) return void 0;  // 如果没有传递数组,或者数组为空,则返回undefined
  if (n == null || guard) return array[0];  // 如果没有传递n或者传递了guard,返回数组第一个元素,这是为了适应map
  return _.initial(array, array.length - n);
};

_.rest、_.tail、_.drop

返回除前n个元素外的其他元素,如果没有传入n或者传入了guard则为除了第一个元素外的其他元素。

_.rest = _.tail = _.drop = function(array, n, guard) {
return slice.call(array, n == null || guard ? 1 : n);
};

_.last

返回最后n个元素,如果没有传入n或者传入了guard则为最后一个元素。

_.last = function(array, n, guard) {
if (array == null || array.length < 1) return void 0;
  if (n == null || guard) return array[array.length - 1];
  return _.rest(array, Math.max(0, array.length - n));
};

_.compact

移除所有等价于false的元素,包括 falsenull,、0""undefined、NaN。

_.compact = function(array) {
return _.filter(array, Boolean);  // 只返回等价为true的元素,会对每个元素进行Boolean转类型
};

flatten

实现可递归的平整化函数,平整化就是将多级嵌套的数组变成单一层级的。

// 传入参数为,输入、是否只执行一层、是否保存非数组元素、输出
var flatten = function(input, shallow, strict, output) {
  output = output || [];  // 如果没有提供输出,则初始化一个空数组
  var idx = output.length;  // 

  for (var i = 0, length = getLength(input); i < length; i++) {
var value = input[i];  // 取当前值
    if (isArrayLike(value) && (_.isArray(value) || _.isArguments(value))) {  // 如果当前值是数组或者arguments

      if (shallow) {  // 如果shallow为true,则只平整化一级
var j = 0, len = value.length;
        while (j < len) output[idx++] = value[j++]; // 依次塞入output中
      } else {
flatten(value, shallow, strict, output);  // 否则递归执行
        idx = output.length;
      }
    } else if (!strict) {  // 如果本身不是类数组对象,且strict为true,则将该值直接塞入output中
      output[idx++] = value;
    }
  }
return output;
};

_.flatten

将数组平整化,默认会递归执行。

_.flatten = function(array, shallow) {
return flatten(array, shallow, false);
};

_.difference

求第一个参数相对于第二个参数的差集,即只存在于第一个参数的数组中而不存在与第二个参数的数组中的元素。

_.difference = restArgs(function(array, rest) {
  rest = flatten(rest, true, true);  // 将第二个参数平整化
  return _.filter(array, function(value){
return !_.contains(rest, value);  // 只返回不包含在第二个参数中的元素
  });
});

_.without

将除第一个参数外的其余参数作为新数组,然后求第一个参数与这个新数组的差集。

_.without = restArgs(function(array, otherArrays) {
return _.difference(array, otherArrays);
});

_.uniq、_.unique

根据传入的数组生成一个新的集合,其中的元素都唯一,其中对象是使用===来判断的。

// 如果第二个参数传入true,表示数组已经有序,这会加快速度
// 如果传入第三个参数iteratee,将根据这个进行遍历
_.uniq = _.unique = function(array, isSorted, iteratee, context) {
  // 如果第二个参数不是传入布尔值,则认为传入的是falss
  // 也就是等价于传入了(array, false, iteratee, context)
  if (!_.isBoolean(isSorted)) {
    context = iteratee;
    iteratee = isSorted;
    isSorted = false;
  }

if (iteratee != null) iteratee = cb(iteratee, context);
  var result = [];
  var seen = [];

  // 依次遍历
  for (var i = 0, length = getLength(array); i < length; i++) {
var value = array[i],  // 初始化为数组第i个值
        computed = iteratee ? iteratee(value, i, array) : value;  // 如果存在iteratee则用它取得计算值,否则直接用当前索引对应的值

    if (isSorted) {  // 如果有序
if (!i || seen !== computed) result.push(value);  // 如果是第一个值,或者上一个计算值不等于当前计算值则直接加入当前值
      seen = computed;  // 上一个值存储为当前计算值
    } else if (iteratee) {  // 否则如果有iteratee
if (!_.contains(seen, computed)) {  // 如果已看过的值中没有当前计算值
        seen.push(computed);  // 已看过的值中加入当前计算值
        result.push(value);  // 结果放入当前值
      }
    } else if (!_.contains(result, value)) {  // 否则直接判断结果中是否有当前值
      result.push(value);
    }
  }
return result;
};

_.union

求传入参数的各个数组的合集。

// 先将所有的参数平整化,然后再唯一化
_.union = restArgs(function(arrays) {
return _.uniq(flatten(arrays, true, true));
});

_.intersection

求传入参数的各个数组的交集。

_.intersection = function(array) {
var result = [];
  var argsLength = arguments.length;

  // 对第一个参数数组里的值进行遍历
  for (var i = 0, length = getLength(array); i < length; i++) {
var item = array[i];
    if (_.contains(result, item)) continue; // 如果结果中已经存在直接跳过

    var j;
    for (j = 1; j < argsLength; j++) {  // 判断在之后参数的数组里是否存在过,如果不存在就跳过
if (!_.contains(arguments[j], item)) break;
    }

if (j === argsLength) result.push(item);  // 如果之后的每个数组都存在过就加入结果中
  }
return result;
};

_.unzip

相当于求矩阵的转置矩阵,原先数组中每个数组的第n个元素元素将组成结果的第n个数组。

_.unzip = function(array) {
var length = array && _.max(array, getLength).length || 0;  // 将参数数组中长度最长的数组的长度作为新数组的长度
  var result = Array(length);

  for (var index = 0; index < length; index++) {
    result[index] = _.pluck(array, index);  // 依次取第index个值放入
  }
return result;
};

_.zip

unzip的逆操作。

_.zip = restArgs(_.unzip);

_.object

将数组转成对象。可以传入键值对组成的数组作为元素的数组,或者第一个数组是键,第二个数组是值。

_.object = function(list, values) {
var result = {};
  for (var i = 0, length = getLength(list); i < length; i++) {
if (values) {
      result[list[i]] = values[i];  // 如过传入第二个参数,则第一个参数是键的数组,第二个参数是值得数组
    } else {
      result[list[i][0]] = list[i][1];  // 否则第一个参数里面的每一个元素都是由键值组成的数组
    }
  }
return result;
};

_.findIndex、_.findLastIndex

寻找符合条件的元素第一次或者最后一次出现的地方。

var createPredicateIndexFinder = function(dir) {
return function(array, predicate, context) {
    predicate = cb(predicate, context);
    var length = getLength(array);
    var index = dir > 0 ? 0 : length - 1;
    // 根据方向遍历
    for (; index >= 0 && index < length; index += dir) {
if (predicate(array[index], index, array)) return index;
    }
return -1;
  };
};

_.findIndex = createPredicateIndexFinder(1);  // 正向查找
_.findLastIndex = createPredicateIndexFinder(-1);  // 反向查找

_.sortedIndex

查找某个value应该插入到数组中的哪个位置来保证数组有序。

// 传递四个参数,分别是要查询的数组、想要判断的对象、迭代器、上下文
_.sortedIndex = function(array, obj, iteratee, context) {
  iteratee = cb(iteratee, context, 1);  // 迭代函数,用来处理迭代
  var value = iteratee(obj);
  var low = 0, high = getLength(array);  // 二分法的高低位设置
  while (low < high) {  // 循环
    var mid = Math.floor((low + high) / 2);
    if (iteratee(array[mid]) < value) low = mid + 1; else high = mid;
  }
  return low;
};


_.indexOf、_.lastIndexOf

寻找某个元素在素组中最先或者最后出现的位置,也是先抽象出一个寻找的函数,然后通过传递不同的方向。

// 传递三个参数,分别是方向、判断函数、查找函数
var createIndexFinder = function(dir, predicateFind, sortedIndex) {

  // 返回的函数有三个参数,分别是要查询的数组、要查询的内容、以及查询的起始位置或者是否排序
  return function(array, item, idx) {

    var i = 0, length = getLength(array);
    if (typeof idx == 'number') {  // 首先如果传递的索引是数字
      if (dir > 0) {  // 如果从前往后
        i = idx >= 0 ? idx : Math.max(idx + length, i);  // 处理索引起点,当输入负数的时候表示从后往前,但转换成从前往后的索引位置
      } else {
        length = idx >= 0 ? Math.min(idx + 1, length) : idx + length + 1;  // 处理查找的最后位置
      }

    } else if (sortedIndex && idx && length) { // 如果有有序查找函数、并且已知数组有序并且非空
      idx = sortedIndex(array, item); // 查找到相应的位置
      return array[idx] === item ? idx : -1;  // 如果该位置就是要查找的内容则返回该位置,否则返回-1
    }


    if (item !== item) {  // 若果item是NaN
      idx = predicateFind(slice.call(array, i, length), _.isNaN);  // 索引是第一个NaN的位置
      return idx >= 0 ? idx + i : -1;  // 如果存在则返回索引,否则返回-1
    }

    // 根据不同方向遍历
    for (idx = dir > 0 ? i : length - 1; idx >= 0 && idx < length; idx += dir) {
      if (array[idx] === item) return idx;  // 如果找到则返回索引
    }
    return -1;  // 找不到返回-1
  };
};

_.indexOf = createIndexFinder(1, _.findIndex, _.sortedIndex);
_.lastIndexOf = createIndexFinder(-1, _.findLastIndex);

_.range

创建一个以start开始,stop结束,间隔为step的数字数组。

_.range = function(start, stop, step) {
if (stop == null) {  // 如果没有传入终止值
    stop = start || 0;  // start值作为终止值
    start = 0;  // 起始值变为零
  }
if (!step) {
    step = stop < start ? -1 : 1;  // 如果没有传入step,间隔根据正反取1或-1
  }

var length = Math.max(Math.ceil((stop - start) / step), 0);  // 向上取整作为数组长度
  var range = Array(length);

  // 遍历,每次增加值为间隔值
  for (var idx = 0; idx < length; idx++, start += step) {
    range[idx] = start;
  }

return range;
};

_.chunk

将数组分成每组count个元素的新数组,最后一个长度可能不到count个。

_.chunk = function(array, count) {
if (count == null || count < 1) return []; // 如果不传入count或者count小于1,返回空数组

  var result = [];
  var i = 0, length = array.length;

  // 每次切割出来count长度的元素
  while (i < length) {
    result.push(slice.call(array, i, i += count));
  }
return result;
};
编辑于 2016-11-07 20:32