本日继承上次的内容,之前我们讲到了 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 一刀切数组。