资讯专栏INFORMATION COLUMN

1625行,解开 underscore.js 的面纱 - 第五章

Rango / 1335人阅读

摘要:对多个一维数组进行并运算,实际上就是加强版的。所以我要说的是这个函数,将传入参数转换为一个数组进行到的回调函数中,以此达到函数接到的是一个一维数组的集合。

每次小章节的开题都烦恼写什么好,所以直接接下文 (~o▔▽▔)~o o~(▔▽▔o~) 。

  _.first = _.head = _.take = function(array, n, guard) {
    if (array == null) return void 0;
    if (n == null || guard) return array[0];
    return _.initial(array, array.length - n);
  };

_.first 用于返回数组中从左到右指定数目 n 的结果集,传入 array、n、guard 三个参数中 array 只能为 Array,当 n = null 时返回数组第一个元素,这里需要讲解的是 _.initial 函数是与 _.first 完全对立的函数,它用于返回数组中从左到右指定数目 Array.length - n 的结果集。

  _.initial = function(array, n, guard) {
    return slice.call(array, 0, Math.max(0, array.length - (n == null || guard ? 1 : n)));
  };

那么它是如何实现的呢,依然是应用数组 Array 的 Array.prototype.slice.call(array, start, end); 实现,这个概念请参看:Array.prototype.slice()。

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

_.last 是返回数组中从右到左指定数目 n 的结果集。实现原理依旧 Array.prototype.slice.call(array, start, end);

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

_.rest 用于返回数组中从右到左指定数目 Array.length - n 的结果集。

  _.compact = function(array) {
    return _.filter(array, Boolean);
  };

_.compact,我喜欢称它为过滤器,过滤坏的数据,那么什么样的数据为坏数据呢,我们可以看下 _.filter,前面讲 _.filter 接收三个参数 obj, predicate, context,其中 predicate 依旧由 cb 处理,那么这里 _.compact 传的 predicate 是 Boolean = function Boolean() { [native code] },这是一个 JAVASCRIPT 内置的函数用于 Boolean 判断,我们可以参考 Boolean 和 Boolean data type。那么重点来了,什么的值会是 Boolean 函数断言为 false 呢,答案就是 false, 0, "", null, undefined, NaN,这个可不是我瞎说或者 copy 官网,我是有理论依据的(vˍv),当当当,看这里 Truthy。

  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))) {
        if (shallow) {
          var j = 0, len = value.length;
          while (j < len) output[idx++] = value[j++];
        } else {
          flatten(value, shallow, strict, output);
          idx = output.length;
        }
      } else if (!strict) {
        output[idx++] = value;
      }
    }
    return output;
  };

flatten 传入四个参数,input, shallow, strict, output,其中我们可以通过 flatten 内部的 for 循环中 length = getLength(input); 知道 input 数据类型为 Array。然后通过对 shallow, strict 两个 Boolean 型变量的控制执行相应的数据处理方式。比如 shallow 为 false 会一直执行 flatten(value, shallow, strict, output);output[idx++] = value; 对多维数组进行一维数组的转换。

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

_.flatten 函数用于对多维度数组进行扁平化处理,即将任意维数的数组转换为一维数组,上面已经说到了这个的实现方式。

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

_.without 用于删除数组中的某些特定元素。它由 _.difference 构成。

  _.uniq = _.unique = function(array, isSorted, 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],
          computed = iteratee ? iteratee(value, i, array) : value;
      if (isSorted) {
        if (!i || seen !== computed) result.push(value);
        seen = computed;
      } else if (iteratee) {
        if (!_.contains(seen, computed)) {
          seen.push(computed);
          result.push(value);
        }
      } else if (!_.contains(result, value)) {
        result.push(value);
      }
    }
    return result;
  };

_.uniq 是数组去重,实现原理是如果 isSorted 及后面元素省略,那么 _.uniq 简化为:

    _.uniq = _.unique = function(array) {
        context = null;
        iteratee = null;
        isSorted = false;
        var result = [];
        var seen = [];
        for (var i = 0, length = getLength(array); i < length; i++) {
          var value = array[i];
          if (!_.contains(result, value)) {
            result.push(value);
          }
        }
        return result;
      };

我们可以看到其核心代码只有 if (!_.contains(result, value)),用于判断数组中是否包含其值,以此达到数组去重的目的。是这里我想说的是 context、iteratee、isSorted 变成了未定义的参数,作者没有处理它会在这种情况下变成全局污染。
接下来我们说一下传入 array, isSorted, iteratee 三个参数的情况,我们已经知道 isSorted 默认为 false,代表去重,那么如果定义 isSorted 为 true 则就是不去重,如果 isSorted 是回调函数,则默认内部重新定义 isSorted 为 false,并将回调函数赋给 iteratee,然后很悲剧的 iteratee 参数依然是没有 var 过的,又污染了啊(‧_‧?) 。大致就是这酱了。

  _.union = restArgs(function(arrays) {
    return _.uniq(flatten(arrays, true, true));
  });

_.union 对多个一维数组进行并运算,实际上就是加强版的 _.uniq。在代码中作者首先用 flatten 函数处理参数,之前我们说到 flatten 是用于多个多维数组进行一位转换,实际上就是要把 arrays 转换。这里有同学可能问道 flatten 直接收一个 Array 剩下的值是 Boolean 啊,那么使用 _.union 的时候是一次性传入 n 个 Array(如这样:_.union([1, 2, 3], [101, 2, 1, 10], [2, 1]);),说不通啊。所以我要说的是 restArgs 这个函数,将传入参数转换为一个数组进行 func.apply(this, args) 到 restArgs 的回调函数 function(arrays) {} 中,以此达到 flatten 函数 arrays 接到的是一个一维数组的集合。最后通过 _.uniq 函数对数组进行处理。

  _.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;
  };

_.intersection 用于获取多个一维数组的相同数据的集合,即交集。又是一番对 Array 的 for 啊 for 啊 for,然后 if 然后 push,相信大家这么聪明,不用多说了,因为这个函数很直白,没太多可讲的。

  _.difference = restArgs(function(array, rest) {
    rest = flatten(rest, true, true);
    return _.filter(array, function(value){
      return !_.contains(rest, value);
    });
  });

_.difference 函数的实现与 _.union 类似,都是通过 restArgs 对 n 个传参进行数组转变,然后赋给回调函数,区别在于这个函数可能更加复杂,它首先 restArgs 回调写了两个传参 array, rest,但实际上 rest 是 undefined,之后在回调内部给 rest 赋值为 flatten 函数处理之后的数组,即扁平化后的一维数组。因为 restArgs 函数只有一个 function 回调,所以内部执行 return func.call(this, arguments[0], rest);,返回的是第一个数组和其他数组的集合,即 array, rest

  _.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);
    }
    return result;
  };

_.unzip 用于将多个数组中元素按照数组下标进行拼接,只接收一个二维数组,返回值同样是一个二维数组。

  _.zip = restArgs(_.unzip);

_.zip_.unzip 不同之处在于它可以传入不定的一维数组参数然后通过 restArgs 函数转换实现 _.unzip 传参的效果。

  _.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;
  };

_.object 用于将数组转换成对象。

  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;
    };
  };

createPredicateIndexFinder 这个函数适用于生成 _.findIndex 之类的函数,当我们看到 return index; 的是后就已经可以知道,其核心是与数组下标有关。

  _.findIndex = createPredicateIndexFinder(1);

_.findIndex 函数由 createPredicateIndexFinder 包装而成,我们可以看到它的默认传值是 1,也就是:

    _.findIndex = function(array, predicate, context) {
       predicate = cb(predicate, context);
       for (var index >= 0; index < getLength(array); index += 1) {
           if (predicate(array[index], index, array)) return index;
       }
       return -1;
   };

其中 predicate 是回调函数接收 array[index], index, array 三个值用于 Boolean 判断,最终结果是返回符合规则的数组中的第一条数据的数组下标。

  _.findLastIndex = createPredicateIndexFinder(-1);

_.findLastIndex 顾名思义就是返回数组中符合规则的最后一条数据的下标,说直白了就是遍历数组的时候从右往左而已。

  _.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;
  };

_.sortedIndex 官网解释说 使用二分查找确定value在list中的位置序号,value按此序号插入能保持list原有的排序。,很绕口,这里我们需要注意的是如果进行 _.sortedIndex 查找这个特定的序列号,一定要事先将 array 进行按需排序。

  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;
      }
      if (item !== item) {
        idx = predicateFind(slice.call(array, i, length), _.isNaN);
        return idx >= 0 ? idx + i : -1;
      }
      for (idx = dir > 0 ? i : length - 1; idx >= 0 && idx < length; idx += dir) {
        if (array[idx] === item) return idx;
      }
      return -1;
    };
  };

createIndexFinder,看命名就可以知道依旧与数组下标有关。我们可以看到数据处理的一个关键是 idx,它可能是一个数字也可能是一个字符串或者对象。当它是 Number 的时候遵循 idx 是限制查找范围的数组下标规则,如果它是其他的则使用 sortedIndex 函数查找到 idx 的数组下标再岁数组查找范围进行限定。

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

_.indexOf 函数与 _.findIndex 区别在于 _.findIndex 需要查找的数据可能存在于数组中也可能不存在数组中,而 _.indexOf 的 predicateFind 一定是数组中的元素。同时也用 array, item, idx 三个参数中的 idx 限定开始查找的范围。

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

_.lastIndexOf 查找数组中的符合结果的最后条数据的数组下标。

  _.range = function(start, stop, step) {
    if (stop == null) {
      stop = start || 0;
      start = 0;
    }
    if (!step) {
      step = stop < start ? -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;
  };

_.range 用于生成一个有序的数组,通过 start 和 stop 限定数组范围,通过 step 限定差值。

  _.chunk = function(array, count) {
    if (count == null || count < 1) return [];
    var result = [];
    var i = 0, length = array.length;
    while (i < length) {
      result.push(slice.call(array, i, i += count));
    }
    return result;
  };

_.chunk,这个函数目前官网并没有释义,估计作者忘记加进去了吧,我们看到 chunk 很自然的就应该想到 stream 的概念,这里也差不多,只不过拆分的不限定是 Buffer 数组, _.chunk 传入两个参数 Array 以及 count,其中 count 用来限定拆分出的每一组的大小,举个栗子:

   _.chunk([1,2,3,4,5,6,7,8,9], 1)
   [[1],[2],[3],[4],[5],[6],[7],[8],[9]]
   _.chunk([1,2,3,4,5,6,7,8,9], 2)
   [[1,2],[3,4],[5,6],[7,8],[9]]

然而但凡对 stream 的概念有所了解都知道这个函数吧,没什么特殊的地方。

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/86341.html

相关文章

  • 如何自制 JS 注释文档生成工具

    摘要:组件的选择命令行工具首先我们需要一个命令行工具来方便的执行命令,这里我们选择组件,如果不喜欢使用且有能力的人完全可以通过组件自己封装执行命令函数。 对于一个成熟的项目而言,一定需要一个注释文档生成工具,我们有很多可选的开源项目,如jsdoc、yuidocjs 等等,拥有这些强大的工具我们完全可以胜任任何注释方面的管理了么? 一个成熟的开发者都会知道不管怎么样的项目都会在不同的开发条件下...

    Cristalven 评论0 收藏0
  • 1625解开 underscore.js 面纱 - 第一章

    摘要:新出台的则规定,包括六种原始类型和,还有一种,详见数据类型和数据结构。用于返回一个由给定对象的所有可枚举自身属性的属性名组成的数组,。接下来判断数字进行相应的操作,其中有和两个方法,详见和。 一直想写一篇这样的文章,于是心动不如行动,这里选择的是 Underscore.js 1.8.3 版本,源码注释加在一起1625行。 Underscore.js 1.8.3 http://unde...

    MockingBird 评论0 收藏0
  • 1625解开 underscore.js 面纱 - 第二章

    摘要:第四个判断如果是对象执行返回一个断言函数,用来判定传入对象是否匹配指定键值属性。都不匹配最后执行,返回传入的对象的属性。设置的值并生成函数,等同于,使具有属性且有值则返回,否则返回,这是一个判断函数。 在第二小章节里面我按照源码顺序介绍几个方法,源码紧接着第一章继续: var builtinIteratee; builtinIteratee,内置的 Iteratee (迭代器)。...

    yuxue 评论0 收藏0
  • 1625解开 underscore.js 面纱 - 第四章

    摘要:接收三个参数分别为回调和,其中与是可选参数。官网释义排序一个列表组成一个组,并且返回各组中的对象的数量的计数。类似,但是不是返回列表的值,而是返回在该组中值的数目。 继续前面的内容,前文我们提到了很多方法的讲解,其实到这里就已经差不多了,因为大部分代码其实都是套路,一些基础函数再灵活变化就可以组成很多实用的功能。 _.sortBy = function(obj, iteratee,...

    zhaochunqi 评论0 收藏0
  • 1625解开 underscore.js 面纱 - 第三章

    摘要:传入值进行判断以此决定函数,将三个参数包括回调传入中其中回调函数充当迭代器进行真值检测,最后。是从一个中随机返回值,并且返回值受限于这个参数,如果没有传入或者传入了则执行语句,目的是将判断处理之后返回单一值。 今天继续上次的内容,之前我们讲到了 reduce 的用法,其实我觉得用法倒是其次的关键是作者实现 reduce 过程中所灵活用到的函数处理方法,我们只要有心稍加总觉完全可以拿来主...

    dack 评论0 收藏0

发表评论

0条评论

最新活动
阅读需要支付1元查看
<