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. 单行文字滚动就用myslider

    单行文字滚动就用myslider,myslider是一个小型的内容滚动jquery插件. 首先请看实例:http://keleyi.com/jq/myslider/demo/4.htm 然后来看代码: ...

  2. if语句,case语句

    1.句式:if...then.判断赋值 例: if RadioButton1.Checked then sex:='男' else if RadioButton2.Checked then sex:= ...

  3. 如何正确响应ArcGIS JavaScript API中图形的鼠标事件

    在使用ArcGIS JavaScript API编写程序的时候,程序员往往需要完成这样一个功能:点击地图上的图形,自动进行专题GIS数据查询,当在地图非图形区域上点击时,自动进行底图兴趣点查询. 由于 ...

  4. Objective-C Runtime 运行时之一:类与对象

    Objective-C语言是一门动态语言,它将很多静态语言在编译和链接时期做的事放到了运行时来处理.这种动态语言的优势在于:我们写代码时更具灵活性,如我们可以把消息转发给我们想要的对象,或者随意交换一 ...

  5. 每个程序员都会的 35 个 jQuery 小技巧

    1. 禁止右键点击 $(document).ready(function(){ $(document).bind("contextmenu",function(e){ return ...

  6. android 双缓存机制

    废话不多说,直接贴代码! 所谓的双缓存,第一就是缓存在内存里面,第二就是缓存在SD卡里面,当你需要加载数据时,先去内存缓存中查找,如果没有再去SD卡中查找,并且用户可以自选使用哪种缓存! 缓存内存和缓 ...

  7. iOS多线程实现4-NSOperation

    原文链接:http://www.cnblogs.com/mddblog/p/4816875.html 一.介绍 NSOperation是一个抽象类,我们可以使用系统提供的子类或者自己实现它的子类,具有 ...

  8. 一个3D ar打飞机的游戏iOS源码

    这是国内目前第一款集合了AR实景,3D游戏和人脸识别的射击游戏,通过旋转和改变手机的角度与位置,所有的射击操作都靠手势来完成,目前所有的源码全部都在这里.appStore地址:https://itun ...

  9. python 函数式编程

    1高阶函数 1.1 变量可以指向函数,可以把一个函数赋值给变量 如下代码 >>> f = abs >>> f(10) 10 1.2 变量可以指向函数,函数的形参也能 ...

  10. 为什么基于TCP UDP服务的一些简单服务端口号都是奇数

    TCP是有NCP(网络控制协议)派生出来的,NCP是单工的,通信需要两个链接,所以会预留奇偶端口号.当TCP/UDP成为传输层协议,每个程序只是用一个端口号,且使用奇端口号.