小知识,大挑战!本文正在参与“程序员必备小知识”创作活动。
今天我们来看看underscore的集合(Collections)相关的方法,主要有ES6相关的each、map、reduce、reduceRight、filter和分组相关的函数等一些函数,一次看完,客官们耐下心来接着往下看。
each
each方法与 ES5 中 Array.prototype.forEach 使用方法类似,遍历数组或者对象的每个元素。这些方法都很简单,直接看代码吧
function each(obj, iteratee, context) {
// 根据 context 确定不同的迭代函数
iteratee = optimizeCb(iteratee, context);
var i, length;
if (isArrayLike(obj)) {
// 数组
for (i = 0, length = obj.length; i < length; i++) {
iteratee(obj[i], i, obj);
}
} else {
// 对象,遍历 keys
var _keys = keys(obj);
for (i = 0, length = _keys.length; i < length; i++) {
iteratee(obj[_keys[i]], _keys[i], obj);
}
}
return obj;
}
map
// Return the results of applying the iteratee to each element.
// 与 ES5 中 Array.prototype.map 使用方法类似
// 传参形式与 _.each 方法类似
// 遍历数组(每个元素)或者对象的每个元素(value)
// 对每个元素执行 iteratee 迭代方法
// 将结果保存到新的数组中,并返回
function map(obj, iteratee, context) {
iteratee = cb(iteratee, context);
var _keys = !isArrayLike(obj) && keys(obj), //对象不是数组,遍历keys
length = (_keys || obj).length, // 如果是数组,_keys = false 然后 取的数组的长度
results = Array(length);
for (var index = 0; index < length; index++) {
// _keys 存在,就是对象,遍历keys数组,取出每个key
var currentKey = _keys ? _keys[index] : index;
results[index] = iteratee(obj[currentKey], currentKey, obj);
}
// 返回新数组
return results;
}
reduce、reduceRight
reduce和reduceRight这两个方法共用了createReduce函数来生成不同的函数,一起来看看怎么提高代码复用度吧。
// **Reduce** builds up a single result from a list of values, aka `inject`,
// or `foldl`.
var reduce = createReduce(1);
// The right-associative version of reduce, also known as `foldr`.
var reduceRight = createReduce(-1);
通过传入不同的值,生成不同的函数返回
// Internal helper to create a reducing function, iterating left or right.
function createReduce(dir) {
// Wrap code that reassigns argument variables in a separate function than
// the one that accesses `arguments.length` to avoid a perf hit. (#1991)
var reducer = function (obj, iteratee, memo, initial) {
var _keys = !isArrayLike(obj) && keys(obj), //对象不是数组,遍历keys
length = (_keys || obj).length, // 如果是数组,_keys = false 然后 取的数组的长度
index = dir > 0 ? 0 : length - 1; //正向遍历还是反向遍历
if (!initial) {
// 没有给初始值,那么 初始值就是 第一个或者最后一个
memo = obj[_keys ? _keys[index] : index];
index += dir; // 下一个的索引
}
for (; index >= 0 && index < length; index += dir) {
var currentKey = _keys ? _keys[index] : index;
// 执行迭代函数,返回函数的返回值
memo = iteratee(memo, obj[currentKey], currentKey, obj);
}
return memo;
};
/**
* obj: 传入的对象
* iteratee: 迭代函数(初始值/返回值、当前值、索引/key、原始数据)
* memo: 初始值
*/
return function (obj, iteratee, memo, context) {
var initial = arguments.length >= 3; // 就是传没传入 memo
return reducer(obj, optimizeCb(iteratee, context, 4), memo, initial);
};
}
filter
// Return all the elements that pass a truth test.
// 寻找数组或者对象中第一个满足条件(predicate 函数返回 true)的元素
// 并返回该元素值
// _.find(list, predicate, [context])
function filter(obj, predicate, context) {
var results = [];
predicate = cb(predicate, context);
// 遍历每一项
each(obj, function (value, index, list) {
// 执行传入的函数,返回值为 true 则 添加进数组
if (predicate(value, index, list)) results.push(value);
});
// 返回满足条件项
return results;
}
shuffle
如何实现一个数组乱序呢,洗牌算法来了。
function sample(obj, n, guard) {
// 没有指定 n 那么就随机返回一个数
if (n == null || guard) {
// 对象,就获取属性值
if (!isArrayLike(obj)) obj = values(obj);
// random 作用是返回随机数[min, max]
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++) {
// 洗牌算法乱序
// 将当前所枚举位置的元素和 `index=rand` 位置的元素交换
var rand = random(index, last);
var temp = sample[index];
sample[index] = sample[rand];
sample[rand] = temp;
}
// 返回 n 个数据
return sample.slice(0, n);
}
// Shuffle a collection.
// 数组乱序
// 如果是对象,则返回一个数组,数组由对象 value 值构成
// Fisher-Yates shuffle 算法
// 最优的洗牌算法,复杂度 O(n)
function shuffle(obj) {
return sample(obj, Infinity);
}
groupBy
// Groups the object's values by a criterion. Pass either a string attribute
// to group by, or a function that returns the criterion.
// _.groupBy(list, iteratee, [context])
// 把一个集合分组为多个集合,通过 iterator 返回的结果进行分组. 如果 iterator 是一个字符串而不是函数, 那么将使用 iterator 作为各元素的属性名来对比进行分组。
// _.groupBy([1.3, 2.1, 2.4], function(num){ return Math.floor(num); }); => {1: [1.3], 2: [2.1, 2.4]}
// _.groupBy(['one', 'two', 'three'], 'length'); => {3: ["one", "two"], 5: ["three"]}
var groupBy = group(function (result, value, key) {
// result 是返回对象; value 是数组元素; key 是迭代后的值
if (has$1(result, key)) result[key].push(value); else result[key] = [value];
});
indexBy
// Indexes the object's values by a criterion, similar to `_.groupBy`, but for
// when you know that your index values will be unique.
// _.indexBy(list, iteratee, [context])
// 给定一个 list,和 一个用来返回一个在列表中的每个元素键 的 iterator 函数(或属性名), 返回一个每一项索引的对象。
// 和 groupBy 非常像,但是当你知道你的键是唯一的时候可以使用 indexBy 。
// var stooges = [{name: 'moe', age: 40}, {name: 'larry', age: 50}, {name: 'curly', age: 60}];
// _.indexBy(stooges, 'age');
// => {
// "40": {name: 'moe', age: 40},
// "50": {name: 'larry', age: 50},
// "60": {name: 'curly', age: 60}
// }
var indexBy = group(function (result, value, key) {
// key 值必须是独一无二的 不然后面的会覆盖前面的
result[key] = value;
});
countBy
// Counts instances of an object that group by a certain criterion. Pass
// either a string attribute to count by, or a function that returns the
// criterion.
// _.countBy(list, iteratee, [context])
// 排序一个列表组成多个组,并且返回各组中的对象的数量的计数。类似 groupBy,但是不是返回列表的值,而是返回在该组中值的数目。
// _.countBy([1, 2, 3, 4, 5], function(num) {
// return num % 2 == 0 ? 'even': 'odd';
// });
// => {odd: 3, even: 2}
var countBy = group(function (result, value, key) {
// 不同 key 值元素数量
if (has$1(result, key)) result[key]++; else result[key] = 1;
});
从上面三个函数中可以看到,共用了group函数,这就是一个很好的地方,就感觉很6,代码封装服用太好了,来看看怎么实现的吧
// An internal function used for aggregate "group by" operations.
// behavior 是一个函数参数
// _.groupBy, _.indexBy 以及 _.countBy 其实都是对数组元素进行分类
// 分类规则就是 behavior 函数
function group(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;
};
}
写文章好难!!!看得懂写不会是咋回事