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

继承前面的内容,前文我们提到了许多要领的解说,实在到这里就已差不多了,因为大部分代码实在都是套路,一些基础函数再天真变化就能够构成许多有效的功用。

  _.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 的设定为牢固的 [[][]],这类写法我以为并非看上去最文雅地,抱负状况是最好不存在第三个参数才对,但这肯定是相对勤俭机能的,面临可勤俭的机能怎样弃取已很清晰了。

    原文作者:kangkk
    原文地址: https://segmentfault.com/a/1190000005678140
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞