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 调用,如何设计地合理,优雅,如何兼顾到各种情况,真的需要强大的实践以及代码能力,这点还需要日后多加摸索。

【跟着子迟品 underscore】JavaScript 数组展开以及重要的内部方法 flatten的更多相关文章

  1. 【跟着子迟品 underscore】JavaScript 中如何判断两个元素是否 "相同"

    Why underscore 最近开始看 underscore.js 源码,并将 underscore.js 源码解读 放在了我的 2016 计划中. 阅读一些著名框架类库的源码,就好像和一个个大师对 ...

  2. 【跟着子迟品 underscore】Array Functions 相关源码拾遗 & 小结

    Why underscore 最近开始看 underscore.js 源码,并将 underscore.js 源码解读 放在了我的 2016 计划中. 阅读一些著名框架类库的源码,就好像和一个个大师对 ...

  3. 【跟着子迟品 underscore】Object Functions 相关源码拾遗 & 小结

    Why underscore 最近开始看 underscore.js 源码,并将 underscore.js 源码解读 放在了我的 2016 计划中. 阅读一些著名框架类库的源码,就好像和一个个大师对 ...

  4. 【跟着子迟品 underscore】如何优雅地写一个『在数组中寻找指定元素』的方法

    Why underscore (觉得这部分眼熟的可以直接跳到下一段了...) 最近开始看 underscore.js 源码,并将 underscore.js 源码解读 放在了我的 2016 计划中. ...

  5. 【跟着子迟品 underscore】for ... in 存在的浏览器兼容问题你造吗

    Why underscore 最近开始看 underscore.js 源码,并将 underscore.js 源码解读 放在了我的 2016 计划中. 阅读一些著名框架类库的源码,就好像和一个个大师对 ...

  6. 【跟着子迟品 underscore】常用类型判断以及一些有用的工具方法

    Why underscore 最近开始看 underscore.js 源码,并将 underscore.js 源码解读 放在了我的 2016 计划中. 阅读一些著名框架类库的源码,就好像和一个个大师对 ...

  7. 【跟着子迟品underscore】从用 `void 0` 代替 `undefined` 说起

    Why underscore 最近开始看 underscore源码,并将 underscore源码解读 放在了我的 2016计划 中. 阅读一些著名框架类库的源码,就好像和一个个大师对话,你会学到很多 ...

  8. JavaScript数组的push()等方法的使用

    数组是值得有序集合.每个值在数组中有一个位置,用数字表示,叫做索引.JavaScript数组是无类型的:数组元素可以是任何类型,而且同一个数组中可以存在不同类型元素,甚至可以是对象或是其他数组,这就可 ...

  9. javascript中静态方法、实例方法、内部方法和原型的一点见解

    1.静态方法的定义 var BaseClass = function() {}; // var BaseClass=new Function(); BaseClass.f1 = function(){ ...

随机推荐

  1. HTML5 Canvas眨眼睛动画

    效果请看: http://keleyi.com/a/bjad/p9exlcwi.htm 请使用支持HTML5的浏览器查看效果. 以下是代码: <html> <body> < ...

  2. HTML5有特色的进度条

    查看效果:http://keleyi.com/keleyi/phtml/html5/26.htm 完整代码如下: <!DOCTYPE html> <html> <head ...

  3. react引用ant的table组件

    import React from 'react';import '../../css/uicss/UI.css';import 'antd/lib/style/index.less';import ...

  4. css2基础知识梳理

    基础的css知识,只放XMind的截图. css01 css02 css03 css04 css05 css+div布局是前端的基本功,要多多练习.运用标准流.浮动.定位.层级等,做简单的静态页面.一 ...

  5. js 理解new的运行机制

    先上段代码: function People(name) { this.name = name; } People.prototype.sayName = function () { console. ...

  6. linux文件目录详解

    文件系统的是用来组织和排列文件存取的,所以她是可见的,在Linux中,我们可以通过ls等工具来查看其结构,在Linux系统中,我们见到 的都是树形结构:比如操作系统安装在一个文件系统中,他表现为由/起 ...

  7. 全文检索引擎 Solr 部署与基本原理

    全文检索引擎 Solr 部署与基本原理 搜索引擎Solr环境搭建实例 关于 solr , schema.xml 的配置说明 全文检索引擎Solr系列-–全文检索基本原理 一.搜索引擎Solr环境搭建实 ...

  8. SwipeRefreshLayout + RecyclerView 实现 上拉刷新 和 下拉刷新

    下拉刷新和上拉刷新都用SwipeRefreshLayout 自带的进度条 布局 <?xml version="1.0" encoding="utf-8"? ...

  9. 我的第一个React Native App

    我用了三天时间实现了一个相对比较完整的React Native 新闻发布类型的示例.应用做得很简单,但大多React Native的组件都有用到,今天做一个分享(由于我电脑是Windows系统,所以只 ...

  10. 为什么我还不推荐内存中OLTP给用户

    嗯,有些人在看玩这篇文章后会恨我,但我还是要说.1个月来我在内存中OLTP这个里领域里做了大量的工作,很多用户都请求使用这个惊艳的新技术.遗憾的是,关于内存中OLTP没有一个是真的令人激动的——看完你 ...