前言

真是天一热什么事都不想干,这个月只产出了一篇文章,赶紧写一篇压压惊!

前文(https://github.com/hanzichi/underscore-analysis/issues/15)说到楼主开始解读 underscore.js 中的 Collection Functions 部分,看了一遍这部分的源码,很多方法都是一看便懂,不需要楼主过分解读。本文来聊聊内部方法 group 的设计,这会是 Collection Functions 部分的第二篇文章,也是最后一篇文章,关于这部分其他方法的实现,可以看我的 全文源码注释

_.groupBy & _.indexBy & _.countBy

group 是 underscore.js 中的一个内部方法,顾名思义,为了分组而用。有三个 API 用到了这个方法,分别是 _.groupBy,_.indexBy,_.countBy。

来看看这三个 API 的作用。

_.groupBy,可以对一个数组的元素进行分组,如何分组?可以将元素传入一个迭代函数,根据迭代后的值进行分组,也可以传入一个字符串表示元素属性,根据该属性值进行分组。

_.groupBy([1.3, 2.1, 2.4], function(num){ return Math.floor(num); });
=> {1: [1.3], 2: [2.1, 2.4]}

_.groupBy(['one', 'two', 'three'], 'length');
=> {3: ["one", "two"], 5: ["three"]}

可以看到,返回的结果是一个对象,key 为经过迭代函数迭代后的值,或者属性值,value 为一个数组,保存迭代结果或者属性值一样的元素。

再来看 _.indexBy

var stooges = [{name: 'moe', age: 40}, {name: 'larry', age: 50}, {name: 'curly', age: 60}];
_.indexBy(stooges, 'age');
=> {
  "40": {name: 'moe', age: 40},
  "50": {name: 'larry', age: 50},
  "60": {name: 'curly', age: 60}
}

跟 _.groupBy 差不多,不同的是,_.indexBy 的结果,每个 key 值对应的是一个元素(传入 _.indexBy 方法的数组中的元素),而不是一个数组(Just like groupBy, but for when you know your keys are unique.)。如果原来数组中,有两个元素,经过迭代后(或者元素属性值)相同,那么在结果对象中,后者会覆盖前者。(所以最好确认数组中的元素经过迭代后的值没有相同的,或者属性值没有相同的)

var tmp = _.indexBy(['one', 'two', 'three'], 'length');
=> Object {3: "two", 5: "three"}

最后来看 _.countBy。还是返回一个结果对象,它的 key 值意思还是和 _.groupBy 以及 _.indexBy 相同,而 value 值为迭代结果(或者属性值)是该 key 值的元素的个数。

_.countBy([1, 2, 3, 4, 5], function(num) {
  return num % 2 == 0 ? 'even': 'odd';
});
=> {odd: 3, even: 2}

group

这三个 API 功能相近,难道要写三个独立的方法?如果独立写,大概会是这样。

_.groupBy = function(obj, iteratee, context) {
  // 返回结果是一个对象
  var result = {};
  // 根据 iteratee 值确定迭代函数
  iteratee = cb(iteratee, context);
  // 遍历元素
  _.each(obj, function(value, index) {
    // 经过迭代,获取结果值,存为 key
    var key = iteratee(value, index, obj);

    // TODO
    // ...
  });
  // 返回结果对象
  return result;
};

_.indexBy = function(obj, iteratee, context) {
  // 返回结果是一个对象
  var result = {};
  // 根据 iteratee 值确定迭代函数
  iteratee = cb(iteratee, context);
  // 遍历元素
  _.each(obj, function(value, index) {
    // 经过迭代,获取结果值,存为 key
    var key = iteratee(value, index, obj);

    // TODO
    // ...
  });
  // 返回结果对象
  return result;
};

_.countBy = function(obj, iteratee, context) {
  // 返回结果是一个对象
  var result = {};
  // 根据 iteratee 值确定迭代函数
  iteratee = cb(iteratee, context);
  // 遍历元素
  _.each(obj, function(value, index) {
    // 经过迭代,获取结果值,存为 key
    var key = iteratee(value, index, obj);

    // TODO
    // ...
  });
  // 返回结果对象
  return result;
};

大堆功能相似的代码,简直不能忍!每当这个时候,就要想到闭包,函数嵌套函数!

首先定义个函数 group,返回一个函数,为以上三个方法能调用的函数。听起来有点拗口,其实就是用 group 做个中间层,上代码体会下。

var group = function() {
  return function(obj, iteratee, context) {
    // 返回结果是一个对象
    var result = {};
    // 根据 iteratee 值确定迭代函数
    iteratee = cb(iteratee, context);
    // 遍历元素
    _.each(obj, function(value, index) {
      // 经过迭代,获取结果值,存为 key
      var key = iteratee(value, index, obj);

      // TODO
      // ...
    });
    // 返回结果对象
    return result;
  };
};

_.groupBy = group();
_.indexBy = group();
_.countBy = group();

其实三个方法主要的操作,就是对上面的 group 中 result 对象的操作,我们可以传入一个方法,利用该方法对 result 对象进行操作。

以 _.groupBy 方法为例:

var group = function(behavior) {
  return function(obj, iteratee, context) {
    // 返回结果是一个对象
    var result = {};
    // 根据 iteratee 值确定迭代函数
    iteratee = cb(iteratee, context);
    // 遍历元素
    _.each(obj, function(value, index) {
      // 经过迭代,获取结果值,存为 key
      var key = iteratee(value, index, obj);
      // operate
      behavior(result, value, key);
    });
    // 返回结果对象
    return result;
  };
};

var behavior = function(result, value, key) {
  if (_.has(result, key))
    result[key].push(value);
  else result[key] = [value];
}

_.groupBy = group(behavior);

将对象当做参数传入函数,能在函数内改变对象值,正是利用了这个特点。而 _.indexBy 和 _.countBy,无非是改一下 behavior 函数的事了。

酱油了一篇解读,下文开始讲扩展函数了,其实最开始有解读 underscore 的欲望,正是因为函数部分的节流和去抖。

浅谈 underscore 内部方法 group 的设计原理的更多相关文章

  1. Unity iOS打开AppStore评星页面,浅谈Application.OpenURL()方法。

    http://fairwoodgame.com/blog/?p=38 Unity iOS打开AppStore评星页面,浅谈Application.OpenURL()方法. Posted in  Uni ...

  2. Java网络编程和NIO详解7:浅谈 Linux 中NIO Selector 的实现原理

    Java网络编程和NIO详解7:浅谈 Linux 中NIO Selector 的实现原理 转自:https://www.jianshu.com/p/2b71ea919d49 本系列文章首发于我的个人博 ...

  3. 浅谈iOS中MVVM的架构设计与团队协作

    说到架构设计和团队协作,这个对App的开发还是比较重要的.即使作为一个专业的搬砖者,前提是你这砖搬完放在哪?不只是Code有框架,其他的东西都是有框架的,比如桥梁等等神马的~在这儿就不往外扯了.一个好 ...

  4. IOS中 浅谈iOS中MVVM的架构设计与团队协作

    今天写这篇文章是想达到抛砖引玉的作用,想与大家交流一下思想,相互学习,博文中有不足之处还望大家批评指正.本篇文章的内容沿袭以往博客的风格,也是以干货为主,偶尔扯扯咸蛋(哈哈~不好好工作又开始发表博客啦 ...

  5. 浅谈iOS中MVVM的架构设计与团队协作【转载】

    今天写这篇文章是想达到抛砖引玉的作用,想与大家交流一下思想,相互学习,博文中有不足之处还望大家批评指正.本篇文章的内容沿袭以往博客的风格,也是以干货为主,偶尔扯扯咸蛋(哈哈~不好好工作又开始发表博客啦 ...

  6. 数据层的多租户浅谈(SAAS多租户数据库设计)

    在上一篇“浅析多租户在 Java 平台和某些 PaaS 上的实现”中我们谈到了应用层面的多租户架构,涉及到 PaaS.JVM.OS 等,与之相应的是数据层也有多租户的支持. 数据层的多租户综述 多租户 ...

  7. [转载]数据层的多租户浅谈(SAAS多租户数据库设计)

    原文:http://www.ibm.com/developerworks/cn/java/j-lo-dataMultitenant/index.html 在上一篇“浅析多租户在 Java 平台和某些 ...

  8. 浅谈MatrixOne如何用Go语言设计与实现高性能哈希表

    目录 MatrixOne数据库是什么? 哈希表数据结构基础 哈希表基本设计与对性能的影响 碰撞处理 链地址法 开放寻址法 Max load factor Growth factor 空闲桶探测方法 一 ...

  9. 浅谈我对DDD领域驱动设计的理解

    从遇到问题开始 当人们要做一个软件系统时,一般总是因为遇到了什么问题,然后希望通过一个软件系统来解决. 比如,我是一家企业,然后我觉得我现在线下销售自己的产品还不够,我希望能够在线上也能销售自己的产品 ...

随机推荐

  1. Mvc传值

    提到Mvc传值我想大多数人想到的是ViewBag,自3.0之后在控制器与视图之间传值绝大多数传值用到的对象就是ViewBag.对于笔者以前做过的一些小的项目,貌似不需要考虑什么,但对于稍微大些的项目涉 ...

  2. SharePoint Framework:下一代开发方式

    SharePoint Framework(SPFx),是页面 和Webpart的模型,完全支持本地开发(即完全可以脱离SharPoint环境在本地进行开发),听起来是不是很高级呢,早期SharePoi ...

  3. [Android]Google 开源的 Android 排版库:FlexboxLayout

    最近Google开源了一个项目叫「FlexboxLayout」. 1.什么是 Flexbox 简单来说 Flexbox 是属于web前端领域CSS的一种布局方案,是2009年W3C提出了一种新的布局方 ...

  4. Java Web总结

    一.地址 客户端路径和服务端路径 表单:<form action="路径"></form> 超链接:<a href="路径"> ...

  5. rails程序文件名命名规范

    1 一般文件名是用小写单词加下划线分割,但类的名字用骆驼法.例如 sessions_controller.rb中定义SessionsController. 2 helpers内的文件为辅助类,定义了许 ...

  6. sp_executesql得到执行sql语句的返回值

    执行 sql语句,得到 变量的值 ' declare @Partition int; ); ); SET @SQLString = N'SELECT @RangeKeyOUT = $PARTITION ...

  7. 基于Ajax+div的“左边菜单、右边内容”页面效果实现

    效果演示: ①默认页面(index.jsp): ②:点击左侧 用户管理 标签下的 用户列表 选项后,右边默认页面内容更新为用户列表页(userList.jsp)的内容 : ③:同理,点击 产品管理.订 ...

  8. 烂泥:Postfix邮件服务器搭建之准备工作

    说实话,Postfix邮件服务器的搭建是一件很麻烦的事情,需要各种软件之间的配置和调试.在写这篇文章之前,我也是搭建测试了不下于10次才算把整个流程给走通,今天刚好有时间把整个搭建过程记录下来. 在正 ...

  9. HTML 5 的自定义 data-* 属性和jquery的data()方法的使用

    人们总喜欢往HTML标签上添加自定义属性来存储和操作数据.但这样做的问题是,你不知道将来会不会有其它脚本把你的自定义属性给重置掉,此外,你这样做也会导致html语法上不符合Html规范,以及一些其它副 ...

  10. DMZ区

    DMZ是英文“Demilitarized Zone”的缩写,它是为了解决安装防火墙后外部网络不能访问内部网络服务器的问题,而设立的一个非安全系统与安全系统之间的缓冲区,这个缓冲区位于企业内部网络和外部 ...