携手创作,共同成长!这是我参与「掘金日新计划 · 8 月更文挑战」的第11天,点击查看活动详情
使用说明
maxBy
lodash里的maxBy方法类似max方法,它接受在迭代的时候调用第二个参数方法,来生成其值排序的标准。
参数说明:
-
参数一是数组,默认是一个对象数组,每一项都是对象。
-
参数二是迭代对比的规则。
当参数二是函数时,return的是对比的标准,该迭代方法的参数是数组的项。
当参数二是字符串时,则代表数组项里的属性。
var objects = [{ 'n': 1 }, { 'n': 2 }];
_.maxBy(objects, function(o) { return o.n; });
// => { 'n': 2 }
_.maxBy(objects, 'n');
// => { 'n': 2 }
从说明中我们明白了其实现是基于之前篇章里的讲到的max方法,在max方法求最大值的过程中,即在每一次遍历中调用第二个参数,参数为数组的项。
minBy
minBy同maxBy方法一致,目的在于求最小的那一项。
var objects = [{ 'n': 1 }, { 'n': 2 }];
_.minBy(objects, function(o) { return o.n; });
// => { 'n': 1 }
// The `_.property` iteratee shorthand.
_.minBy(objects, 'n');
// => { 'n': 1 }
手写实现
maxBy
手写maxBy方法,考虑到该方法的第二项可以传递两种类型,所以我们可以做如下封装:
function maxBy(array, iteratee) {
if (!Array.isArray(array)) return undefined
if (typeof iteratee !== 'function' && typeof iteratee !== "string") return array[0]
const length = array.length;
let maxValue = null
let maxIndex = 0
let handle = null
if (typeof iteratee === "function") {
handle = item => iteratee(item)
maxValue = iteratee(array[0])
} else {
handle = item => item[iteratee]
maxValue = array[0][iteratee]
}
for (let i = 1; i < length; i++) {
if (maxValue < handle(array[i])) {
maxValue = handle(array[i])
maxIndex = i
}
}
return array[maxIndex]
}
我们第一步先进行严谨性判断,当出现特例情况时对其进行指定数据的返回。
当参数满足要求后,我们对第二个参数进行判断,当该参数是字符串时选择读取,当该参数是函数时选择调用。
当封装好handle方法之后,我们则根据max的实现思路进行遍历、对比、存储,最后返回指定的项。
minBy
minBy同maxBy的区别在于比较时的基准,更改比较符号即可。
function minBy(array, iteratee) {
if (!Array.isArray(array)) return undefined
if (typeof iteratee !== 'function' && typeof iteratee !== "string") return array[0]
const length = array.length;
let minValue = null
let minIndex = 0
let handle = null
if (typeof iteratee === "function") {
handle = item => iteratee(item)
minValue = iteratee(array[0])
} else {
handle = item => item[iteratee]
minValue = array[0][iteratee]
}
for (let i = 1; i < length; i++) {
if (minValue > handle(array[i])) {
minValue = handle(array[i])
minIndex = i
}
}
return array[minIndex]
}
源码实现
maxBy
查看lodash里的maxBy,我们看到当第一个参数只有存在并且身上具有length属性时才会调用baseExtremum方法,否则返回undefined。
function maxBy(array, iteratee) {
return (array && array.length)
? baseExtremum(array, baseIteratee(iteratee, 2), baseGt)
: undefined;
}
在之前的篇章中我们求极值便是用到了baseExtremum方法。而该方法正是对一组可循环操作的数据进行遍历、对比、存储。
function baseExtremum(array, iteratee, comparator) {
var index = -1,
length = array.length;
while (++index < length) {
var value = array[index],
current = iteratee(value);
if (current != null && (computed === undefined
? (current === current && !isSymbol(current))
: comparator(current, computed)
)) {
var computed = current,
result = value;
}
}
return result;
}
baseGt代码如下,主要是判断第一个值是否大于第二个值。
function baseGt(value, other) {
return value > other;
}
对比求极值,我们更加关注的是每一次对比的过程,以及如何对比。
对于maxBy,我们更加关注的是第二个参数的使用。
在lodash里,对比操作被封装到一个baseIteratee的方法里,因为我们并不清楚maxBy第二个参数传入的类型,这部分实现类似于我们上面实现的maxBy方法里对第二个参数的判断过程。
function baseIteratee(value) {
if (typeof value == 'function') {
return value;
}
if (value == null) {
return identity;
}
if (typeof value == 'object') {
return isArray(value)
? baseMatchesProperty(value[0], value[1])
: baseMatches(value);
}
return property(value);
}
其中baseIteratee运用了下面封装的工具方法:
// 判断数组
var isArray = Array.isArray;
// 获取赋值
function identity(value) {
return value;
}
关于对象和数组的判断以及后续处理逻辑,在maxBy中暂时不需要,因为封装的baseIteratee在后续需要迭代处理的方法中需要用到。
关于property方法,对于maxBy第二个参数为字符串类型的情况则可调用该方法,该方法判断是否符合对象key值,即基本数据类型。
function property(path) {
return isKey(path) ? baseProperty(toKey(path)) : basePropertyDeep(path);
}
function toKey(value) {
if (typeof value == 'string' || isSymbol(value)) {
return value;
}
var INFINITY = 1 / 0;
var result = (value + '');
return (result == '0' && (1 / value) == -INFINITY) ? '-0' : result;
}
function isKey(value, object) {
if (isArray(value)) {
return false;
}
var reIsDeepProp = /.|[(?:[^[]]*|(["'])(?:(?!\1)[^\]|\.)*?\1)]/,
reIsPlainProp = /^\w*$/;
var type = typeof value;
if (type == 'number' || type == 'symbol' || type == 'boolean' ||
value == null || isSymbol(value)) {
return true;
}
return reIsPlainProp.test(value) || !reIsDeepProp.test(value) ||
(object != null && value in Object(object));
}
maxBy第二个参数字符串类型下则调用baseProperty方法,该方法返回一个函数,该函数接受的对象类型,即在maxBy里遍历时传入的数组项,所以maxBy第二个参数为字符串类型下,实际在迭代处理时字符串被处理成函数方法,迭代处理时仍调用的是函数。
function baseProperty(key) {
return function(object) {
return object == null ? undefined : object[key];
};
}
minBy
同理与maxBy的唯一区别则是在对比时的符号,换个方向就行了,maxBy内部在调用baseExtremum时传入baseGT,在这里我们传入baseLt即可。
function minBy(array, iteratee) {
return (array && array.length)
? baseExtremum(array, baseIteratee(iteratee, 2), baseLt)
: undefined;
}
function baseLt(value, other) {
return value < other;
}
小结
maxBy和minBy同max和min方法实现思路一致,并且在迭代数组的时候都用到了baseExtremum方法。而baseExtremum只管迭代数组并调用第二个参数,所以第二个参数为函数类型。
对于maxBy和minBy,关键则在于对各自身上的第二个参数类型进行转换处理,字符串类型最终会转换成函数类型给迭代时调用。
这也是identity方法封装的必要性,因为传入的参数并不清楚类型,兼容性处理则可以在后面的方法中进行增强。如果当初在遍历数组的时候直接对current进行赋值操作而非函数调用,那就没办法使用增强功能了,即字符串处理成函数的情况无法实现。
所以封装并非一簇而成的,而是在发现通用性的基础上思考可拓展性。