资讯专栏INFORMATION COLUMN

1625行,解开 underscore.js 的面纱 - 第四章

zhaochunqi / 1674人阅读

摘要:接收三个参数分别为回调和,其中与是可选参数。官网释义排序一个列表组成一个组,并且返回各组中的对象的数量的计数。类似,但是不是返回列表的值,而是返回在该组中值的数目。

继续前面的内容,前文我们提到了很多方法的讲解,其实到这里就已经差不多了,因为大部分代码其实都是套路,一些基础函数再灵活变化就可以组成很多实用的功能。

  _.sortBy = function(obj, iteratee, context) {
    var index = 0;
    iteratee = cb(iteratee, context);
    return _.pluck(_.map(obj, function(value, key, list) {
      return {
        value: value,
        index: index++,
        criteria: iteratee(value, key, list)
      };
    }).sort(function(left, right) {
      var a = left.criteria;
      var b = right.criteria;
      if (a !== b) {
        if (a > b || a === void 0) return 1;
        if (a < b || b === void 0) return -1;
      }
      return left.index - right.index;
    }), "value");
  };

_.sortBy,顾名思义这是一个对数组进行排序处理的函数,在原生 JAVASCRIPT 中 sort() 的详情可参考 Array.prototype.sort()、TypedArray.prototype.sort()。_.sortBy 接收三个参数分别为 obj、iteratee 回调和 context,其中 iteratee 与 context 是可选参数。
当传入值只有 obj 时,应该限定 obj 类型为数组且值为 Number,为什么呢,这里涉及到 JAVASCRIPT 对数字字符串的比较的问题了,JAVASCRIPT 在进行字符串比较的时候遵循的是二进制与运算,也就是说并不是数字 length 越长就会大于 length 小的。举个栗子:

  _.sortBy([1, 2, 3, 4, 5, 6, 8, 7, 11, 13]);
  [1, 2, 3, 4, 5, 6, 7, 8, 11, 13]
  _.sortBy(["1", "2", "3", "4", "5", "6", "8", "7", "11", "13"]);
  ["1", "11", "13", "2", "3", "4", "5", "6", "7", "8"]

同学们都很聪明,不用我在说了,言归正传,当只有 obj 一个值且值为 Number,那么默认从左到右从小到大排序,为什么呢,我看下代码,在 _.pluck 中代码只做了一件事,就是整理数据,当没有 iteratee 的时候执行 cb 函数里的 if (value == null) return _.identity; 也就是相当于默认 iteratee function 为 _.identity 即 return obj,所以 _.map 中回调的 criteria 值即 value。有点绕口,代码起开(假定只有 obj 一个参数):

   _.sortBy = function(obj) {
     var index = 0;
     return _.pluck(_.map(obj, function(value, key, list) {
       return {
         value: value,
         index: index++,
         criteria: (function(value, key, list) {
             return value;
           })(value, key, list);
       };
     }).sort(function(left, right) {
       var a = left.criteria;
       var b = right.criteria;
       if (a !== b) {
         if (a > b || a === void 0) return 1;
         if (a < b || b === void 0) return -1;
       }
       return left.index - right.index;
     }), "value");
   };

这样看上去就直白好多。整理完数据之后就是 arr.sort([compareFunction]) 进行排序,这里不说了。当传入参数有 iteratee 回调的时候,依旧老套路优化回调,然后根据回调函数里面的设定决定 criteria 参数值,criteria 参数是 arr.sort([compareFunction]) 进行排序的关键标识,so一定要是 Number才行。

  var group = function(behavior, partition) {
    return function(obj, iteratee, context) {
      var result = partition ? [[], []] : {};
      iteratee = cb(iteratee, context);
      _.each(obj, function(value, index) {
        var key = iteratee(value, index, obj);
        behavior(result, value, key);
      });
      return result;
    };
  };

group 是一个内部函数,我觉得它最特别在于将回调称之为一个 behavior,为什么呢,因为虽然 behavior function 只能被动接受 value, index, obj 三个参数进行数值运算,但作者巧妙的用它结合 group 包装出 _.groupBy_.indexBy_.countBy_.partition 四个函数,在实际开发中我们处理数据时可能需要各种适用场景的工具,那么把如何函数写好写活呢,group 给了我很大的启发,言归正传,group 的 behavior 回调是在外部定义,源码到这里并不知道 behavior 是什么东西,所以先一带而过。

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

_.groupBy 官网定义把一个集合分组为多个集合,通过 iterator 返回的结果进行分组. 如果 iterator 是一个字符串而不是函数, 那么将使用 iterator 作为各元素的属性名来对比进行分组.

———————— 颓废的分割线 ————————

从昨天到今天状态不佳,昏天黑地的看了两天电影,看到最后都不知道自己在看什么,我需要吐槽一下小米路由器,由于我是 linux 系统,作为 deiban 死忠党来说一台不到两千元的台式机想要链接无线网络,折腾的时间和金钱都不如再填个路由器做中继划算,于是我买了这货 小米路由器,它在路由器模式下还算可以,一但调整到中继模式,这完全就是一个入坑的神展开,啪啪啪的随时无间歇性断网没商量,莫名其妙的就连不上网了,即使连接上网络网速都不如无线的一般有木有,在过去的一段时间里我有 N 次想把这款路由器摔在地上(额,或者摔在墙上),希望大家不要吐槽我两千块都不到的台式主力机,价钱虽然 lower 了点,但性能绝对够用,对于 mac 党们我很希望大家转粉,虽然我也有 mac 但是我平均开机数目大约在 1/(1~2个月)。

写到这里目测大约水了一百多个文字,继续前天的讲解 ╮(╯Д╰)╭ 。

———————— END ————————

官网的意思是什么呢,假如我有一个 obj,那么我可以使用 _.groupBy 函数将这个 obj 通过其内部值的某个属性进行分类,而这个属性值的判断也可以通过回调进行扩展断言。那么当 iteratee 为 null 时,_.groupBy 默认使用前面的 group 函数中的 cb 函数的 if (value == null) return _.identity; 处理 iteratee 为空的情况,我来简化一下 _.groupBy

 _.groupBy = function(obj) {
    var result = partition ? [[], []] : {};
    _.each(obj, function(value, index) {
         var key = value;
        if (_.has(result, key)) result[key].push(value); else result[key] = [value];
    })
    return result;
}

这样理解是不是浅显很多呢,设置 result 空数组,然后 _.each 遍历 obj,满满的都是套路有木有,唯一亮点的地方就是 if 判断是根据 _.has 函数确定 result 中是否已经存在 key-value。但是这里面还有一个更深的套路,那就是作者没有对 obj 作进一步处理,所以 _.groupBy 函数只能适用于 Array,举个栗子:

  _.groupBy(["one", "two", "three"]);
  {"one":["one"],"two":["two"],"three":["three"]}
  _.groupBy([{a:"one"}, {b:"two"}, {c:"three"}]);
  {"[object Object]":[{"a":"one"},{"b":"two"},{"c":"three"}]}

然后我们再说一下 _.groupBy 参数有第二个参数的情况,这里可以看出 cb 函数的重要性,它对 iteratee 的类型情况做了细致的判断和处理,我们前面可以知道 cb 函数除了 Null、Function、Object 意外的类型都用 _.property 处理,即 生成获取属性值的函数,那么我们传参为数组呢,see ↓↓↓

 _.groupBy(["one", "two", "three"],[1,2,3])
 {"false":["one","two","three"]}

也就是说作者虽然大才,但是并没有对超出范围的值类型做进一步的处理,也就是说 iteratee 的可选值类型只能为 Function 和 String。当然这并不是错,从工具的角度来讲我们应用函数应该遵守函数创造者设定的规则,超出规则后出现错误并不是说作者的函数一定有问题,也可能是我们太过于调皮了(比如番茄西红柿需要用平底锅来炒,但厨师非要用电饭煲,这是厨师的错还是平底锅生产商的错 ─=≡Σ((( つ•̀ω•́)つ)。

言归正传当传入合理的 iteratee 值时,其实整个函数的重点还是 group 函数内部的 cb 函数,因为我们可以看源码 _.groupBy 上的回调最终是落实到 cb 上,将一个函数比作一个公共房间,众多人就是传入传出的参数,那么 cb 就是门禁卡识别每个人的身份并发身份牌。如果 iteratee 是 String 则用 _.property 处理恰到好处(生成获取属性值的函数),如果是 Function 也只是在 if (_.has(result, key)) result[key].push(value); else result[key] = [value]; 之前通过回调生成相应的 key 值。

  _.indexBy = group(function(result, value, key) {
    result[key] = value;
  });

官网释义 给定一个list,和 一个用来返回一个在列表中的每个元素键 的iterator 函数(或属性名),返回一个每一项索引的对象。关键代码参考 _.groupBy,二者的二区别也之有一行代码,理解起来并不难,我就不再水文字了。

  _.countBy = group(function(result, value, key) {
    if (_.has(result, key)) result[key]++; else result[key] = 1;
  });

官网释义 排序一个列表组成一个组,并且返回各组中的对象的数量的计数。类似groupBy,但是不是返回列表的值,而是返回在该组中值的数目。其实就是对匹配成功的元素计数。

  var reStrSymbol = /[^ud800-udfff]|[ud800-udbff][udc00-udfff]|[ud800-udfff]/g;

reStrSymbol 用于正则函数,这一块我也不是很熟悉,但是我找到了两篇文章做了参考,Unicode Regular Expressions, Surrogate Points and UTF-8、
Re: Java char and Unicode 3.0+ (was:Canonical equivalence in rendering: mandatory or recommended?)、unicode。另外知乎上也有人对这句话做了判断:

 [^ud800-udfff] 普通的 BMP 字符,表示不包含代理对代码点的所有字符
 [ud800-udbff][udc00-udfff] 成对的代理项对,表示合法的代理对的所有字符
 [ud800-udfff] 未成对的代理项字,表示代理对的代码点(本身不是合法的Unicode字符)

以上仅供参考,我也不是很清楚,等我做好这方面功课的时候再重新说这个话题。

  _.toArray = function(obj) {
    if (!obj) return [];
    if (_.isArray(obj)) return slice.call(obj);
    if (_.isString(obj)) {
      return obj.match(reStrSymbol);
    }
    if (isArrayLike(obj)) return _.map(obj, _.identity);
    return _.values(obj);
  };

官网说 把list(任何可以迭代的对象)转换成一个数组,在转换 arguments 对象时非常有用,并给出一个 (function(){ return _.toArray(arguments).slice(1); })(1, 2, 3, 4);,说心里话每当看到 arguments 的时候我第一个印象是 Array.prototype.slice.call(arguments, indexes);,这里作者对待 Array 的原理同样是这个。_.toArray 函数本身没有重点,无非就是根据字符串、数组、对象进行数组转换,需要注意的是当转换 Object 的时候会忽略 key-value 的 key,只多带带把 value 放到数组中,另外就是 if (_.isArray(obj))if (isArrayLike(obj)),顾名思义第一个是判断数组,第二个难道是考虑到 {"length":[1,2,3,4]} 这种数据结构的情况?

  _.size = function(obj) {
    if (obj == null) return 0;
    return isArrayLike(obj) ? obj.length : _.keys(obj).length;
  };

_.size 用于返回传入参数的长度,包括但不限于 Object、Array 、 String 和 Function,Function 返回的是 Function 中传入参数的个数(arguments)。另外 Map 这里有个坑,Map返回值是12,众所周知 Map是一个大的对象,所以返回值是它的12个基本属性的个数。

  _.partition = group(function(result, value, pass) {
    result[pass ? 0 : 1].push(value);
  }, true);

_.partition 是第四个用 group 函数包装的函数,用来对传入 obj 做判断时返回符合回调断言的结果集以及不符合的结果集,从 result[pass ? 0 : 1].push(value) 这里就可见一斑了,也就是说 group 的第三个传参 partition 也就是为了 _.partition 而存在。partition 使 result 的设定为固定的 [[][]],这种写法我觉得并不是看上去最优雅地,理想情况是最好不存在第三个参数才对,但这一定是相对节约性能的,面对可节约的性能怎么取舍已经很清楚了。

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/86345.html

相关文章

  • 如何自制 JS 注释文档生成工具

    摘要:组件的选择命令行工具首先我们需要一个命令行工具来方便的执行命令,这里我们选择组件,如果不喜欢使用且有能力的人完全可以通过组件自己封装执行命令函数。 对于一个成熟的项目而言,一定需要一个注释文档生成工具,我们有很多可选的开源项目,如jsdoc、yuidocjs 等等,拥有这些强大的工具我们完全可以胜任任何注释方面的管理了么? 一个成熟的开发者都会知道不管怎么样的项目都会在不同的开发条件下...

    Cristalven 评论0 收藏0
  • 1625解开 underscore.js 面纱 - 第二章

    摘要:第四个判断如果是对象执行返回一个断言函数,用来判定传入对象是否匹配指定键值属性。都不匹配最后执行,返回传入的对象的属性。设置的值并生成函数,等同于,使具有属性且有值则返回,否则返回,这是一个判断函数。 在第二小章节里面我按照源码顺序介绍几个方法,源码紧接着第一章继续: var builtinIteratee; builtinIteratee,内置的 Iteratee (迭代器)。...

    yuxue 评论0 收藏0
  • 1625解开 underscore.js 面纱 - 第一章

    摘要:新出台的则规定,包括六种原始类型和,还有一种,详见数据类型和数据结构。用于返回一个由给定对象的所有可枚举自身属性的属性名组成的数组,。接下来判断数字进行相应的操作,其中有和两个方法,详见和。 一直想写一篇这样的文章,于是心动不如行动,这里选择的是 Underscore.js 1.8.3 版本,源码注释加在一起1625行。 Underscore.js 1.8.3 http://unde...

    MockingBird 评论0 收藏0
  • 1625解开 underscore.js 面纱 - 第三章

    摘要:传入值进行判断以此决定函数,将三个参数包括回调传入中其中回调函数充当迭代器进行真值检测,最后。是从一个中随机返回值,并且返回值受限于这个参数,如果没有传入或者传入了则执行语句,目的是将判断处理之后返回单一值。 今天继续上次的内容,之前我们讲到了 reduce 的用法,其实我觉得用法倒是其次的关键是作者实现 reduce 过程中所灵活用到的函数处理方法,我们只要有心稍加总觉完全可以拿来主...

    dack 评论0 收藏0
  • 1625解开 underscore.js 面纱 - 第六章

    摘要:用来构成和两个函数,主要针对的是为了将函数调用模式更改为构造器调用和方法调用。通过函数设定时间为毫秒后执行函数的回调函数,用以达到在规定时间毫秒时执行函数的目的,并且规定时间内只执行一次函数。 北京的雨已经断断续续下了好久,昏昏欲睡的躲在家里不愿意出门,火影忍者快要结束了,一拳超人第二季据说还要等好多年,勇者大冒险貌似断更了,我又是在不喜欢海贼王的画风,所以,我该看什么好呢。 va...

    v1 评论0 收藏0

发表评论

0条评论

zhaochunqi

|高级讲师

TA的文章

阅读更多
最新活动
阅读需要支付1元查看
<