今天继续上次的内容,之前我们讲到了 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。
_.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
。
_.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
一样,但是不知道作者为什么没有这样做,可能有其他的考虑吧,这里不再揣测。
_.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。
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])
有所区别,但也有很多相通之处。
_.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 方法,虽然代码很复杂,但目前实际上只应用了如下简化的结构:
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 一刀切数组。