今天继续上次的内容,以前咱们讲到了 reduce
的用法,其实我以为用法却是其次的关键是做者实现 reduce
过程当中所灵活用到的函数处理方法,咱们只要有心稍加总觉彻底能够拿来主义,丰富本身的代码└(^o^)┘。数组
_.find = _.detect = function(obj, predicate, context) { var keyFinder = isArrayLike(obj) ? _.findIndex : _.findKey; var key = keyFinder(obj, predicate, context); if (key !== void 0 && key !== -1) return obj[key]; };
_.find
,讨论这个函数首先要弄懂 _.findIndex
和 _.findKey
,这里咱们先简单知道一个是针对数组一个是针对对象,具体的后面读到源码再说。传入值 obj 进行 isArrayLike 判断以此决定 keyFinder 函数,将三个参数包括回调传入 keyFinder 中其中 predicate 回调函数充当迭代器进行真值检测,最后 return obj[key]。闭包
var createPredicateIndexFinder = function(dir) { return function(array, predicate, context) { predicate = cb(predicate, context); var length = getLength(array); var index = dir > 0 ? 0 : length - 1; for (; index >= 0 && index < length; index += dir) { if (predicate(array[index], index, array)) return index; } return -1; }; };
以 _.findIndex
为例简单介绍一下,_.findIndex
是由 createPredicateIndexFinder 包装而成,意义在于返回 predicate 函数内部 return true。app
_.filter = _.select = function(obj, predicate, context) { var results = []; predicate = cb(predicate, context); _.each(obj, function(value, index, list) { if (predicate(value, index, list)) results.push(value); }); return results; };
_.filter
函数与 _.find
相似,内部实现较之 _.find
更简单些,_.find
意为匹配 predicate 回调 return true 惟一就近值,_.filter
则是匹配全部值的集合。那么有人说为何不用 _.filter()[0]
取代 _.find
,理论上两者确实是相同值,可是 _.filter
会遍历传参 obj 直至结束,而 _.find
则是遍历过程当中匹配成功结束遍历,因此某些状况下 _.find
优于 _.filter
。dom
_.reject = function(obj, predicate, context) { return _.filter(obj, _.negate(cb(predicate)), context); };
_.reject
,经过 _.negate
和 cb
函数包装 predicate 回调,实际上就是用 optimizeCb
优化 predicate function,而后用 _.negate
返回与 predicate 相反的 Boolean 类型值,以此得到与 _.filter
做用相反的结果集合。函数
_.every = _.all = function(obj, predicate, context) { predicate = cb(predicate, context); var keys = !isArrayLike(obj) && _.keys(obj), length = (keys || obj).length; for (var index = 0; index < length; index++) { var currentKey = keys ? keys[index] : index; if (!predicate(obj[currentKey], currentKey, obj)) return false; } return true; };
_.every
,咱们看源码中的返回值类型为 Boolean 知道这是一个用于真值检测的函数,内部的处理步骤已经很程序化了,首先优化回调函数 predicate,处理传参 obj(根据 Object 或者 Array),回调中接收 obj[currentKey], currentKey, obj
三个参数进行 Boolean 判断,当判断失败的时候则 if (!false) return false;
结束 for 循环。这个方法看上去很鸡肋,但实际上结合 predicate 回调应用于某些判断处理很给力。优化
_.some = _.any = function(obj, predicate, context) { predicate = cb(predicate, context); var keys = !isArrayLike(obj) && _.keys(obj), length = (keys || obj).length; for (var index = 0; index < length; index++) { var currentKey = keys ? keys[index] : index; if (predicate(obj[currentKey], currentKey, obj)) return true; } return false; };
_.some
,看源码咱们能够知道它基本上与 _.every
相似,区别在于 _.some
遍历 obj 过程当中只要任何一个元素经过 predicate 回调的真值检测就直接当即中断遍历并返回 true。我主观意识上更偏向于 _.every
和 _.some
用一个相同的基础函数包装再经过判断值构建它们,就像 createReduce
函数构成 _.reduce
、_.reduceRight
同样,可是不知道做者为何没有这样作,可能有其余的考虑吧,这里再也不揣测。this
_.contains = _.includes = _.include = function(obj, item, fromIndex, guard) { if (!isArrayLike(obj)) obj = _.values(obj); if (typeof fromIndex != 'number' || guard) fromIndex = 0; return _.indexOf(obj, item, fromIndex) >= 0; };
_.contains
用于检查 obj 中是否包含 item 值,我更倾向于这是一个简化版的 _.some
,若是是我写基础函数可能真的就只有 _.some
不用 _.contains
,可是 Undescore.js 做为一个知名函数库,在代码优化的执行速度上确定要比咱们作的更细。
这里顺便说一下 _.indexOf
和 guard
,_.indexOf
是由 createIndexFinder 包装而来,能够理解为数组版的 indexOf,indexOf 概念可参考 String.prototype.indexOf() 和 Array.prototype.indexOf()。关于 array.indexOf(searchElement[, fromIndex = 0])
,我这里再说几句,这个 JAVASCRIPT 函数传入1或2个参数,第一个参数为将要进行匹配的内容,可为 Number 可为 String,第二个可选参数为(须要定向匹配数组中某一值的数组下标值 - array.length)*n,且 n!= 0
,array.indexOf
根据这个下标进行定向匹配验证,若是匹配成功则返回值为被匹配值的数组下标,匹配失败则返回 -1。prototype
var array = [2, 9, 9,9,9,3,4]; undefined array.indexOf(9,2); 2 array.indexOf(9,3); 3 array.indexOf(9,4); 4 array.indexOf(9,5); -1 array.indexOf(3,5); 5 array.indexOf(5); -1 array.indexOf(2, -7); 0
_.indexOf
虽然与 array.indexOf(searchElement[, fromIndex = 0])
有所区别,但也有不少相通之处。rest
_.invoke = restArgs(function(obj, method, args) { var isFunc = _.isFunction(method); return _.map(obj, function(value) { var func = isFunc ? method : value[method]; return func == null ? func : func.apply(value, args); }); });
_.invoke
用于批量执行方法,前面咱们讲了 restArgs 方法,虽然代码很复杂,但目前实际上只应用了以下简化的结构:code
var restArgs = function(func) { return function() { return func.apply(this, arguments); }; };
也就是说 _.invoke
抛开闭包的概念以后等同于:
function(obj, method, args) { var isFunc = _.isFunction(method); return _.map(obj, function(value) { var func = isFunc ? method : value[method]; return func == null ? func : func.apply(value, args); }); }
其中 _.isFunction
是判断是否为 function,接下来 _.map
回调,实际上我很纳闷万一传入的 method 是 obj[i] 对象上没有的方法怎么办,按照 return 的结果若是没有则返回 func 也就是 null
,总以为这样返回缺乏点什么。
_.pluck = function(obj, key) { return _.map(obj, _.property(key)); };
_.pluck
返回传入 obj 的 key 的集合,或者说 key 的集合有点武断,更具体点说是 obj 下第二层所包含 key 的值的集合,而第一层也就是 obj 可为 Object 或 Array,但 obj 中第二层必须是 Object。这是为何呢?
_.map(obj, function(key) { return (function(obj) { return obj == null ? void 0 : obj[key]; })(key); })
在上述简化的代码中咱们能够看出 return obj == null ? void 0 : obj[key];
的值是 obj[key],因此第二层只能是 Object。
_.where = function(obj, attrs) { return _.filter(obj, _.matcher(attrs)); };
_.where
颇有趣,代码简化以后是:
_.where = function(obj, attrs) { return _.filter(obj, (function(attrs) { attrs = _.extendOwn({}, attrs); return function(obj) { return _.isMatch(obj, attrs); })(attrs); }); };
_.filter
咱们讲过是获取全部匹配值的集合,而回调中的 _.extendOwn
将 attrs 放入空对象 {}
中并 return,_.isMatch
是个断言用于判断 obj 中是否存在 key-value。那么 _.where
就是 _.isMatch
和 _.filter
的增强版,它用于判断一个大的对象数组中存在与传入 attrs 相同的键值对,若是存在则返回匹配目标键值对所在的 Object,而且返回值是一个集合。
var list = [{author:"Shakespeare",title:"china"}, {author:"Shakespeare",year:1611,title:"china"}, {author:"Shakespeare",year:1611,title:"English"}, {year:1611,title:"china"}]; _.where(list, {author: "Shakespeare", year: 1611}); [{"author":"Shakespeare","year":1611,"title":"china"},{"author":"Shakespeare","year":1611,"title":"English"}]
这个方法在处理数据的时候特别有用。
_.findWhere = function(obj, attrs) { return _.find(obj, _.matcher(attrs)); };
_.findWhere
,至关于 _.where()[0]
,即返回结果集合的第一个值,这么设定的目的和 _.find
与 _.filter
同样,运算更快,遍历到目标立刻中止遍历。
_.max = function(obj, iteratee, context) { var result = -Infinity, lastComputed = -Infinity, value, computed; if (iteratee == null || (typeof iteratee == 'number' && typeof obj[0] != 'object') && obj != null) { obj = isArrayLike(obj) ? obj : _.values(obj); for (var i = 0, length = obj.length; i < length; i++) { value = obj[i]; if (value != null && value > result) { result = value; } } } else { iteratee = cb(iteratee, context); _.each(obj, function(v, index, list) { computed = iteratee(v, index, list); if (computed > lastComputed || computed === -Infinity && result === -Infinity) { result = v; lastComputed = computed; } }); } return result; };
_.max
用来查找 obj 对象数组中某一 key 的最大值的 Object,限定是 key-value 的 value 必须是 Number 类型。-Infinity
我更喜欢叫它负无穷,这里的 if true 第一个判断能够忽略了,为何不讲了呢,由于做者要放弃 typeof iteratee == 'number' && typeof obj[0] != 'object'
这种状况,可见其余版本的 Underscore.js。若是忽略 typeof iteratee == 'number' && typeof obj[0] != 'object'
的状况则 _.max
传参为一个数组,return 为数组中最大值。if false 则进行常规的 _.each
代码很简单这里再也不讲解。
_.min = function(obj, iteratee, context) { var result = Infinity, lastComputed = Infinity, value, computed; if (iteratee == null || (typeof iteratee == 'number' && typeof obj[0] != 'object') && obj != null) { obj = isArrayLike(obj) ? obj : _.values(obj); for (var i = 0, length = obj.length; i < length; i++) { value = obj[i]; if (value != null && value < result) { result = value; } } } else { iteratee = cb(iteratee, context); _.each(obj, function(v, index, list) { computed = iteratee(v, index, list); if (computed < lastComputed || computed === Infinity && result === Infinity) { result = v; lastComputed = computed; } }); } return result; };
_.min
真心不用讲了,参考 _.max
。
_.shuffle = function(obj) { return _.sample(obj, Infinity); };
_.shuffle
官网释义是返回一个随机乱序的 list 副本, 使用 Fisher-Yates shuffle 来进行随机乱序.
,Fisher-Yates shuffle
是什么鬼,咱们这里看到 _.shuffle
这个函数用到了 _.sample
,因此咱们先讲 _.sample
。
_.sample = function(obj, n, guard) { if (n == null || guard) { if (!isArrayLike(obj)) obj = _.values(obj); return obj[_.random(obj.length - 1)]; } var sample = isArrayLike(obj) ? _.clone(obj) : _.values(obj); var length = getLength(sample); n = Math.max(Math.min(n, length), 0); var last = length - 1; for (var index = 0; index < n; index++) { var rand = _.random(index, last); var temp = sample[index]; sample[index] = sample[rand]; sample[rand] = temp; } return sample.slice(0, n); };
_.sample
是从一个 obj 中随机返回值,而且返回值受限于 n 这个参数,若是没有传入 n 或者传入了 guard = true 则执行 if 语句,目的是将 obj 判断处理以后返回单一值。这里以为特鸡肋有木有,也就是说 _.sample(obj,n,true)
和_.sample(obj)
是一回事。若是按照 _.sample(obj,n)
的逻辑执行,依赖是老套路,处理 obj (Object 和 Array),而后 n = Math.max(Math.min(n, length), 0);
得到合理的 n 值,前面咱们讲到了 Infinity
正无穷和 -Infinity
负无穷,这段代码利用了 Infinity 的特性包装了 _.shuffle
函数,关键就是 Infinity 大于全部 Number 数字,即 Math.min(Infinity, Number)
等于 Number,好处就是让人眼前一亮,哇,原来代码还能够这样写,坏处就是当单独使用 _.sample
函数的 n 大于处理以后的 obj 的长度时并不会报错,而是默认执行 n=sample.length
,仁者见仁,智者见智吧。后面就是很套路的根据数组下标替换数组内容,固然数组下标是经过 _.random
随机的,而后 slice 一刀切数组。