继续前面的内容,前文咱们提到了不少方法的讲解,其实到这里就已经差很少了,由于大部分代码其实都是套路,一些基础函数再灵活变化就能够组成不少实用的功能。html
_.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 小的。举个栗子:linux
_.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 做为各元素的属性名来对比进行分组.
。并发
———————— 颓废的分割线 ————————ide
从昨天到今天状态不佳,昏天黑地的看了两天电影,看到最后都不知道本身在看什么,我须要吐槽一下小米路由器,因为我是 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 的设定为固定的 [[][]]
,这种写法我以为并非看上去最优雅地,理想状况是最好不存在第三个参数才对,但这必定是相对节约性能的,面对可节约的性能怎么取舍已经很清楚了。