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. JavaScript小细节点罗列

    共勉! 属性访问表达式 众所周知,JavaScript为属性的访问定义了两种语法方式: 表达式.标识符 // 表达式(指定对象) 标识符(指定需要访问的属性的名称) 表达式[表达式] //表达式1(指 ...

  2. 获取OpenFileDialog的文件名和文件路径

    得到文件名 string fileName = ofd.SafeFileName; 得到路径 string filePath = System.IO.Path.GetDirectoryName(ofd ...

  3. Android开发7:简单的数据存储(使用SharedPreferences)和文件操作

    前言 啦啦啦~大家好,又见面啦~ 本篇博文讲和大家一起完成一个需要注册.登录的备忘录的,一起学习 SharedPreferences 的基本使用,学习 Android 中常见的文件操作方法,复习 An ...

  4. AngularJS中的指令全面解析(转载)

    说到AngularJS,我们首先想到的大概也就是双向数据绑定和指令系统了,这两者也是AngularJS中最为吸引人的地方.双向数据绑定呢,感觉没什么好说的,那么今天我们就来简单的讨论下AngularJ ...

  5. SharePoint 2013 工作流之年假审批Designer配置篇

    本文介绍SharePoint 2013 使用Designer工具,设计年假审批工作流,由于流程所用的条件和操作都比较简单,所以演示为主,最后附流程图和流程的文本图,有兴趣的可以参照实验.如果对于Des ...

  6. drawRect与setNeedsDisplay简单介绍

    - (void)drawRect:(CGRect)rect { } p.p1 { margin: 0.0px 0.0px 0.0px 0.0px; font: 14.0px Menlo; color: ...

  7. Intent属性详解三 data、type和extra

    1 Data  执行时要操作的数据 在目标<data/>标签中包含了以下几种子元素,他们定义了url的匹配规则: android:scheme 匹配url中的前缀,除了“http”.“ht ...

  8. iOS json解析的几种方法 NSJSONSerialization,JSONKit,SBJson ,TouchJson

    相关的第三方类库大家可以去github上下载 1.NSJSONSerialization 具体代码如下 : - (void)viewDidLoad { [super viewDidLoad]; NSD ...

  9. windows server2012 R2 本地策略编辑

    进入本地策略编辑器: 1.win + R 2.输入命令行:gpedit.msc 密码期限设置: 1.windows设置 2.安全设置 3.账户策略 4.密码策略 5.密码最长使用期限 赋值 0 交互登 ...

  10. JavaScript的个人学习随手记(一)

    JavaScript 简介  要学习的人可以到W3School http://www.w3school.com.cn/b.asp JavaScript 是世界上最流行的编程语言. 这门语言可用于 HT ...