JavaScript 数组睁开以及 underscore 主要的内部要领 flatten 详解

Why underscore

(以为这一段眼熟的童鞋能够直接跳到正文了…)

近来最先看 underscore.js 源码,并将 underscore.js 源码解读 放在了我的 2016 设想中。

浏览一些有名框架类库的源码,就好像和一个个巨匠对话,你会学到许多。为何是 underscore?最主要的原因是 underscore 简短精干(约 1.5k 行),封装了 100 多个有效的要领,耦合度低,异常合适逐一要领浏览,合适楼主如许的 JavaScript 初学者。从中,你不仅能够学到用 void 0 替代 undefined 防止 undefined 被重写等一些小技能 ,也能够学到变量范例推断、函数撙节&函数去抖等经常运用的要领,还能够学到许多浏览器兼容的 hack,更能够学到作者的团体设想思绪以及 API 设想的道理(向后兼容)。

以后楼主会写一系列的文章跟人人分享在源码浏览中进修到的学问。

迎接围观~ (假如有兴致,迎接 star & watch~)您的关注是楼主继承写作的动力

flatten

端五歇息三天,睡了两天,是该有点产出了。

本日要讲的是数组睁开以及和数组睁开息息相关的一个主要的内部要领 flatten。

什么是数组睁开?简朴的说就是将嵌套的数组 “摊平”,照样举几个简朴的例子吧。

[[[1, 2], [1, 2, 3]], [1, 2]] => [1, 2, 1, 2, 3, 1, 2]
[[[1, 2], [1, 2, 3]], [1, 2]] => [[1, 2], [1, 2, 3], 1, 2]

以上两种都是数组睁开,第一种我们以为是深度睁开,即突破一切嵌套数组,将元素提取出来放入一个数组中;第二种只睁开了一层,即只把数组内嵌套的一层数组睁开,而没有递归睁开下去。

我们起首来看看 flatten 要领的挪用情势。

var flatten = function(input, shallow, strict, startIndex) {
  // ...
};

第一个参数 input 即为须要睁开的数组,所以 flatten 要领中传入的第一个参数肯定是数组(或许 arguments);第二个参数 shallow 是个布尔值,假如为 false,则示意数组是深度睁开,假如为 true 则示意只睁开一层;第四个参数示意 input 睁开的肇端位置,即从 input 数组中第几个元素最先睁开。

var ans = flatten([[1, 2], [3, 4]], false, false, 1);
console.log(ans); // => [3, 4]

从第 1 项最先睁开数组,即疏忽了数组的第 0 项([1, 2])。

以上三个参数照样比较轻易明白的,相对来说费力的是第三个参数 strict。strict 也是个布尔值,当 shallow 为 true 而且 strict 也为 true 时,能过滤 input 参数元素中的非数组元素。好难明白啊!我们举个简朴的例子。

var ans = flatten([5, 6, [1, 2], [3, 4]], true, true);
console.log(ans); // => [1, 2, 3, 4]

5 和 6 是 input 参数中的非数组元素,直接过滤掉了。假如 strict 为 true 而且 shallow 为 false,那末挪用 flatten 要领的效果只能是 []。所以我们会看到源码里假如 strict 为 true,那末 shallow 也一定是 true。

直接来看源码,加了异常多的解释。

var flatten = function(input, shallow, strict, startIndex) {
  // output 数组保留效果
  // 即 flatten 要领返回数据
  // idx 为 output 的累计数组下标
  var output = [], idx = 0;

  // 依据 startIndex 变量肯定须要睁开的肇端位置
  for (var i = startIndex || 0, length = getLength(input); i < length; i++) {
    var value = input[i];
    // 数组 或许 arguments
    if (isArrayLike(value) && (_.isArray(value) || _.isArguments(value))) {
      // flatten current level of array or arguments object
      // (!shallow === true) => (shallow === false)
      // 则示意需深度睁开
      // 继承递归睁开
      if (!shallow) 
        // flatten 要领返回数组
        // 将上面定义的 value 从新赋值
        value = flatten(value, shallow, strict);

      // 递归睁开到末了一层(没有嵌套的数组了)
      // 或许 (shallow === true) => 只睁开一层
      // value 值肯定是一个数组
      var j = 0, len = value.length;

      // 这一步貌似没有必要
      // 毕竟 JavaScript 的数组会自动扩大
      // 然则如许写,觉得比较好,关于元素的 push 历程有个比较清楚的熟悉
      output.length += len;

      // 将 value 数组的元素添加到 output 数组中
      while (j < len) {
        output[idx++] = value[j++];
      }
    } else if (!strict) { 
      // (!strict === true) => (strict === false)
      // 假如是深度睁开,即 shallow 参数为 false
      // 那末当末了 value 不是数组,是基础范例时
      // 肯定会走到这个 else-if 推断中
      // 而假如此时 strict 为 true,则不能跳到这个分支内部
      // 所以 shallow === false 假如和 strict === true 搭配
      // 挪用 flatten 要领获得的效果永远是空数组 []
      output[idx++] = value;
    }
  }

  return output;
};

总的来说,就是延续递归挪用 flatten,直到不能睁开为止。给出 flatten 要领的完成源码位置 https://github.com/hanzichi/underscore-analysis/blob/master/underscore-1.8.3.js/src/underscore-1.8.3.js#L489-L507

接着我们来看看源码中有效到这个内部要领的 API。

起首是 _.flatten 要领,异常简朴,用了 flatten 的前三个参数。

_.flatten = function(array, shallow) {
  // array => 须要睁开的数组
  // shallow => 是不是只睁开一层
  // false 为 flatten 要领 strict 变量
  return flatten(array, shallow, false);
};

前面说了,strict 为 true 只和 shallow 为 true 一同运用,所以没有特殊情况的话 strict 默以为 false。

_.union 要领一样用到了 flatten,这个要领的作用是传入多个数组,然后对数组元素去重。

var ans = _.union([[1]], [1, 2], 3, 4);
console.log(ans); // => [[1], 1, 2]

起首并不须要对数组深度睁开,其次 _.union 传入的是数组,关于非数组元素能够直接疏忽。这两点直接对应了 shallow 参数和 strict 参数均为 true(都不用做容错处理了)。关于一个数组的去重,末了挪用 _.unique 即可。

_.union = function() {
  // 起首用 flatten 要领将传入的数组睁开成一个数组
  // 然后就能够愉快地挪用 _.uniq 要领了
  // 假定 _.union([1, 2, 3], [101, 2, 1, 10], [2, 1]);
  // arguments 为 [[1, 2, 3], [101, 2, 1, 10], [2, 1]]
  // shallow 参数为 true,睁开一层
  // 效果为 [1, 2, 3, 101, 2, 1, 10, 2, 1]
  // 然后对其去重
  return _.uniq(flatten(arguments, true, true));
};

而 _.difference,_.pick,_.omit 要领,人人能够本身进源码去看,都迥然不同,没什么迥殊要注意的点。(注意下 startIndex 参数即可)

关于内部要领 flatten,我要总结的是,能够某个内部要领会被多个 API 挪用,怎样设想地合理,文雅,怎样兼顾到各种情况,真的须要壮大的实践以及代码才能,这点还须要往后多加探索。

    原文作者:fish
    原文地址: https://segmentfault.com/a/1190000005691641
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞